Motivación¶
La industria musical ha estado en ascenso por décadas, últimamente la forma más popular para consumir música es a través de plataformas tales como Spotify, Apple Music, Soundcloud, etc. Siendo el más popular de estas Spotify, es interesante ver cómo el éxito de ciertos géneros/artistas ha cambiado gracias a esta forma de consumo, siendo importantes los algoritmos de recomendación a usuarios, las playlists hechas por la plataforma para destacar artistas de cierto género y el compartir tu música con los demás.
Es impresionante ver cómo las playlists mencionadas anteriormente creadas por Spotify tales como "Viva Latino" "Rock Classics" "K-Pop ON!", etc. Cuentan cada una con más de cinco millones de likes otorgados por usuarios que disfrutan de dichos específicos géneros, esta forma de categorización de la música es incluso tan importante para la plataforma que en el menú de exploración principal es el filtro recomendado para buscar tu música preferida, sugiriéndolo por sobre una búsqueda por artista o década.
Creemos que, dado esto y la gran cantidad de datos disponibles de la plataforma, es posible estudiar una conexión entre características cuantificables de las canciones y el género al que estas pertenecerán.
Análisis exploratorio¶
Antes de comenzar, es importante mencionar que en el dataset cada género presente en este aparece 1000 veces, por lo tanto, no hay un género predilecto que pueda crear sesgos en la exploración de datos.
Género de las canciones más populares¶
Cada canción en el dataset posee un porcentaje de popularidad como atributo (siendo 100 la más popular). Este es calculado por un algoritmo basado en su mayoría en el número total de reproducciones y cuán recientes estas son.
En el gráfico "género de las 300 canciones más populares" (ver en código análisis exploratorio) se muestra la cantidad de apariciones de los géneros musicales en las 300 canciones con la popularidad más alta. De aquí se elegirían los géneros musicales a estudiar, ya que nos muestra los más relevantes y exitosos en términos de popularidad en la actual industria musical. Debido a que son los más escuchados, nos gustaría poder clasificar canciones de estos géneros para poder recomendar las canciones de dichas características adecuadamente a los usuarios.
Se contempló la idea de elegir géneros basados en el promedio de popularidad de estos tomando en cuenta todas las canciones del dataset, pero se concluyó que no es la información que buscamos, pues al ser un promedio, puede haber géneros con unas pocas canciones con popularidad muy alta que eleven el resultado por el hecho de promediar. Además, como podemos ver, hay géneros que apelan a grupos muy específicos de gente con la que siempre les va bien, por lo que el promedio de popularidad se dispara a pesar de no ser un género popular con las masas. Un ejemplo de esto es el sertanejo, que es el noveno en el gráfico, pero no es conocido.
Matrices de correlación por género¶
Las matrices de correlación por géneros musicales (ver en la parte de código análisis exploratorio) se hacen con el objetivo de ver diferencias en las características musicales que nos puedan ayudar a diferenciar a través de métricas un género del otro.
Con esto nos damos cuenta de que entre los 30 géneros más populares hay unos que se comportan de manera similar a otros y algunos que son todo lo contrario. Por ejemplo, alt-rock se comporta de manera similar al género rock. Es por esto que decidimos no elegir a los géneros a trabajar de acuerdo a nuestro criterio, sino más bien crear un modelo que los agrupe según lo parecido que son, para no tener sesgos en el proyecto.
Cantidad de canciones explicitas y no explicitas por género¶
Al analizar el gráfico de canciones explícitas/no explícitas por género musical es posible observar que existen géneros sin canciones con palabras explícitas, así como también al comparar entre canciones explícitas y no explícitas para un mismo género es común que existan más canciones no explícitas; sin embargo, hay excepciones (en particular las que alcanzan 'no explícitas' mayores a 5 para log en base e) en donde la cantidad de canciones explícitas es similar a las no explícitas (emo, j-dance, sad), en donde incluso se tiene que para el género comedy hay más canciones explícitas que no explícitas.
Esto indica que la característica 'explicit' puede ser un fuerte criterio para identificar estos géneros al momento de querer realizar el modelo.
Duración promedio por género¶
Al analizar el gráfico, es posible observar que los géneros chicago-house, minimal-techno y detroit-techno son los que poseen una mayor duración (sobre los 5 minutos), mientras que grindcore, children y study son los de menor duración (bajo los 2 minutos). Además, la mayoría de los géneros se encuentran en el rango de los 3 a los 4 minutos de duración.
A partir de estas observaciones, se puede analizar que para canciones con una duración entre 3 y 4 minutos será más difícil clasificar a qué género pertenecen basándose en este criterio. Sin embargo, resultará más útil para las canciones cuyos géneros correspondan a una duración promedio muy corta o muy larga.
Análisis mediante funcionalidad describe()¶
Dentro de las medidas que nos interesa analizar, se encuentran el mínimo y máximo para conocer el rango de valores, además del promedio y la concentración de datos mediante los percentiles.
Este análisis estará centrado en las características musicales 'popularity' y 'loudness' que no se han estudiado antes mediante las matrices de correlación.
Para el caso de 'popularity', el promedio de los datos es 33 de 100, siendo una medida relativamente baja, y al observar los percentiles comprobamos esto, pues la popularidad es menor o igual a 17, 35 y 50 para los percentiles p25, p50 y p75, respectivamente. Esto nos indica que hay una gran concentración de datos con popularidad baja y que, por ende, el criterio de 'popularity' podría no ser un buen indicador para clasificar correctamente los géneros.
Para 'loudness', el rango va desde -49.53 hasta 4.53, y a través de los percentiles se observa que hay una mejor distribución para este parámetro. Por ejemplo, un 25% de los datos varían entre un rango alto como lo es desde -49.53 hasta -10.01. Mientras que los otros cuartiles toman rangos menores con diferencias de 3, 2 y 9 en la escala de 'loudness'. Con esto y junto con la medida de la desviación estándar (5.029), observamos que los valores varían considerablemente para este criterio, y que por ello, podría resultar útil para la clasificación de géneros.
Preguntas y problemas¶
(1) ¿Es posible predecir el género al que pertenece una canción de acuerdo a las variables musicales tales como el tempo, la valencia musical, cantidad de beats, entre otros?
Utilidad: Es de utilidad para que dadas las características musicales provenientes de la canción de una persona, esta sea capaz de identificar cómo será generalmente clasificada su canción con respecto a otras similares.
(2) ¿Existen géneros que, pese a ser distintos, sus canciones tienen atributos similares? ¿Y viceversa?
Utilidad: Gracias a esto se podrán agrupar los 114 géneros del dataset en clusters para así obtener los grupos de géneros más importantes del dataset, es decir, aquellos que aportan una característica diferente notoria a una canción.
(3) ¿Los géneros con más canciones explícitas comparten características entre ellos?
Utilidad: Permitirá saber si es un criterio de utilidad para identificar géneros con esta característica en común, y en dicho caso indagar en otros atributos musicales que permitan construir modelos, como por ejemplo los árboles de decisión.
Código análisis exploratorio¶
Código¶
%%capture
!pip install datasets
Paquetes¶
# Dataset
from datasets import load_dataset
# pandas
import pandas as pd
# numpy
import numpy as np
# graficos
import matplotlib.pyplot as plt
from matplotlib import ticker
from matplotlib.ticker import FuncFormatter
import seaborn as sns
import plotly.express as px
Cargar dataset¶
# load dataset from url
%%capture
dataset = load_dataset("maharshipandya/spotify-tracks-dataset")
df = pd.DataFrame(dataset['train'])
df
Cantidad de canciones por género¶
# cantidad de géneros del dataset
genre_most_repeat = df['track_genre'].value_counts(dropna=False).reset_index()
genre_most_repeat.columns = ['genre', 'count']
genre_most_repeat
# Crear un gráfico de barras
plt.figure(figsize=(15,6))
plt.bar(genre_most_repeat['genre'], genre_most_repeat['count'])
# Establecer etiquetas de los ejes
plt.xlabel('Género musical')
plt.ylabel('Cantidad de apariciones en el dataset')
# Establecer título
plt.title('Cantidad de géneros en el dataset')
# Rotar etiquetas del eje x para mejor visualización
plt.xticks(rotation=90, ha='right')
# Mostrar el gráfico
plt.show()
Género de las canciones más populares¶
Géneros de las 300 canciones más populares del dataset¶
#tomar los 300 mas populares
top_100 = df.nlargest(300, 'popularity')
#grafico de los generos de los 300 mas populares
genre_counts = top_100['track_genre'].value_counts()
plt.figure(figsize=(10, 6))
genre_counts.plot(kind='bar', color= 'pink')
plt.xlabel('tipo de genero')
plt.ylabel('Cantidad')
plt.title('Género de las 300 canciones más populares')
plt.xticks(rotation=45, ha='right')
plt.tight_layout()
plt.show()
Promedio de popularidad por género¶
# Promedio de la popularidad por género musical
popularity_genre = df.groupby('track_genre')['popularity'].mean().reset_index()
popularity_genre = popularity_genre.sort_values(by='popularity')
# Crear un gráfico de barras
plt.figure(figsize=(15,6))
plt.bar(popularity_genre['track_genre'], popularity_genre['popularity'])
# Establecer etiquetas de los ejes
plt.xlabel('Género musical')
plt.ylabel('Popularidad promedio')
# Establecer título
plt.title('Popularidad promedio de los géneros musicales')
# Rotar etiquetas del eje x para mejor visualización
plt.xticks(rotation=90, ha='right')
# Mostrar el gráfico
plt.show()
Matrices de correlación por género¶
Matriz de correlacion para género pop¶
# Filtrar el DataFrame por género (por ejemplo, pop)
pop_df = df[df['track_genre'] == 'pop']
# Calcular la matriz de correlación para el género "pop"
correlation_matrix = pop_df[['danceability', 'energy', 'speechiness', 'acousticness', 'instrumentalness', 'liveness', 'valence', 'tempo']].corr()
# Crear un mapa de calor para visualizar la matriz de correlación
plt.figure(figsize=(10, 8))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', fmt=".2f")
plt.title('Matriz de Correlación entre Características genero pop')
plt.show()
Matriz de correlacion para género dance¶
# Filtrar el DataFrame por género (por ejemplo, dance)
dance_df = df[df['track_genre'] == 'dance']
# Calcular la matriz de correlación para el género "dance"
correlation_matrix = dance_df[['danceability', 'energy', 'speechiness', 'acousticness', 'instrumentalness', 'liveness', 'valence', 'tempo']].corr()
# Crear un mapa de calor para visualizar la matriz de correlación
plt.figure(figsize=(10, 8))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', fmt=".2f")
plt.title('Matriz de Correlación entre Características genero dance')
plt.show()
Matriz de correlación de género latino¶
# Filtrar el DataFrame por género (por ejemplo, latino)
latino_df = df[df['track_genre'] == 'latino']
# Calcular la matriz de correlación para el género "latino"
correlation_matrix = latino_df[['danceability', 'energy', 'speechiness', 'acousticness', 'instrumentalness', 'liveness', 'valence', 'tempo']].corr()
# Crear un mapa de calor para visualizar la matriz de correlación
plt.figure(figsize=(10, 8))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', fmt=".2f")
plt.title('Matriz de Correlación entre Características genero latino')
plt.show()
Matriz de correlación de género reggaeton¶
# Filtrar el DataFrame por género (por ejemplo, reggaeton)
reggaeton_df = df[df['track_genre'] == 'reggaeton']
# Calcular la matriz de correlación para el género "reggaeton"
correlation_matrix = reggaeton_df[['danceability', 'energy', 'speechiness', 'acousticness', 'instrumentalness', 'liveness', 'valence', 'tempo']].corr()
# Crear un mapa de calor para visualizar la matriz de correlación
plt.figure(figsize=(10, 8))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', fmt=".2f")
plt.title('Matriz de Correlación entre Características genero reggaeton')
plt.show()
Matriz de correlacion de género hip-hop¶
# Filtrar el DataFrame por género (por ejemplo, hip-hop)
hip_hop_df = df[df['track_genre'] == 'hip-hop']
# Calcular la matriz de correlación para el género "hip-hop"
correlation_matrix = hip_hop_df[['danceability', 'energy', 'speechiness', 'acousticness', 'instrumentalness', 'liveness', 'valence', 'tempo']].corr()
# Crear un mapa de calor para visualizar la matriz de correlación
plt.figure(figsize=(10, 8))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', fmt=".2f")
plt.title('Matriz de Correlación entre Características genero hip_hop')
plt.show()
Matriz de correlacion de género reggae¶
# Filtrar el DataFrame por género (por ejemplo, reggae)
reggae_df = df[df['track_genre'] == 'reggae']
# Calcular la matriz de correlación para el género "reggae"
correlation_matrix = reggae_df[['danceability', 'energy', 'speechiness', 'acousticness', 'instrumentalness', 'liveness', 'valence', 'tempo']].corr()
# Crear un mapa de calor para visualizar la matriz de correlación
plt.figure(figsize=(10, 8))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', fmt=".2f")
plt.title('Matriz de Correlación entre Características genero reggae')
plt.show()
Matriz de correlacion de género rock¶
# Filtrar el DataFrame por género (por ejemplo, rock)
rock_df = df[df['track_genre'] == 'rock']
# Calcular la matriz de correlación para el género "rock"
correlation_matrix = rock_df[['danceability', 'energy', 'speechiness', 'acousticness', 'instrumentalness', 'liveness', 'valence', 'tempo']].corr()
# Crear un mapa de calor para visualizar la matriz de correlación
plt.figure(figsize=(10, 8))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', fmt=".2f")
plt.title('Matriz de Correlación entre Características genero rock')
plt.show()
Matriz de correlacion de género edm¶
# Filtrar el DataFrame por género (por ejemplo, edm)
edm_df = df[df['track_genre'] == 'edm']
# Calcular la matriz de correlación para el género "edm"
correlation_matrix = edm_df[['danceability', 'energy', 'speechiness', 'acousticness', 'instrumentalness', 'liveness', 'valence', 'tempo']].corr()
# Crear un mapa de calor para visualizar la matriz de correlación
plt.figure(figsize=(10, 8))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', fmt=".2f")
plt.title('Matriz de Correlación entre Características genero edm')
plt.show()
Matriz de correlación de género latin¶
# Filtrar el DataFrame por género (por ejemplo, latin)
latin_df = df[df['track_genre'] == 'latin']
# Calcular la matriz de correlación para el género "latin"
correlation_matrix = latin_df[['danceability', 'energy', 'speechiness', 'acousticness', 'instrumentalness', 'liveness', 'valence', 'tempo']].corr()
# Crear un mapa de calor para visualizar la matriz de correlación
plt.figure(figsize=(10, 8))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', fmt=".2f")
plt.title('Matriz de Correlación entre Características genero latin')
plt.show()
Matriz de correlacion de género electro¶
# Filtrar el DataFrame por género (por ejemplo, electro)
electro_df = df[df['track_genre'] == 'electro']
# Calcular la matriz de correlación para el género "electro"
correlation_matrix = electro_df[['danceability', 'energy', 'speechiness', 'acousticness', 'instrumentalness', 'liveness', 'valence', 'tempo']].corr()
# Crear un mapa de calor para visualizar la matriz de correlación
plt.figure(figsize=(10, 8))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', fmt=".2f")
plt.title('Matriz de Correlación entre Características genero electro')
plt.show()
Matriz de correlación de género indie¶
# Filtrar el DataFrame por género (por ejemplo, indie)
indie_df = df[df['track_genre'] == 'indie']
# Calcular la matriz de correlación para el género "indie"
correlation_matrix = indie_df[['danceability', 'energy', 'speechiness', 'acousticness', 'instrumentalness', 'liveness', 'valence', 'tempo']].corr()
# Crear un mapa de calor para visualizar la matriz de correlación
plt.figure(figsize=(10, 8))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', fmt=".2f")
plt.title('Matriz de Correlación entre Características genero indie')
plt.show()
Matriz de correlacion de género alternative¶
# Filtrar el DataFrame por género (por ejemplo, alternative)
alternative_df = df[df['track_genre'] == 'alternative']
# Calcular la matriz de correlación para el género "alternative"
correlation_matrix = alternative_df[['danceability', 'energy', 'speechiness', 'acousticness', 'instrumentalness', 'liveness', 'valence', 'tempo']].corr()
# Crear un mapa de calor para visualizar la matriz de correlación
plt.figure(figsize=(10, 8))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', fmt=".2f")
plt.title('Matriz de Correlación entre Características genero alternative')
plt.show()
Matriz de correlacion de género house¶
# Filtrar el DataFrame por género (por ejemplo, house)
house_df = df[df['track_genre'] == 'house']
# Calcular la matriz de correlación para el género "house"
correlation_matrix = house_df[['danceability', 'energy', 'speechiness', 'acousticness', 'instrumentalness', 'liveness', 'valence', 'tempo']].corr()
# Crear un mapa de calor para visualizar la matriz de correlación
plt.figure(figsize=(10, 8))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', fmt=".2f")
plt.title('Matriz de Correlación entre Características genero house')
plt.show()
Matriz de correlación de género alt-rock¶
# Filtrar el DataFrame por género (por ejemplo, alt-rock)
rock_df = df[df['track_genre'] == 'alt-rock']
# Calcular la matriz de correlación para el género "alt-rock"
correlation_matrix = rock_df[['danceability', 'energy', 'speechiness', 'acousticness', 'instrumentalness', 'liveness', 'valence', 'tempo']].corr()
# Crear un mapa de calor para visualizar la matriz de correlación
plt.figure(figsize=(10, 8))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', fmt=".2f")
plt.title('Matriz de Correlación entre Características genero alt-rock')
plt.show()
Matriz de correlación de género garage¶
# Filtrar el DataFrame por género (por ejemplo, garage)
garage_df = df[df['track_genre'] == 'garage']
# Calcular la matriz de correlación para el género "garage"
correlation_matrix = garage_df[['danceability', 'energy', 'speechiness', 'acousticness', 'instrumentalness', 'liveness', 'valence', 'tempo']].corr()
# Crear un mapa de calor para visualizar la matriz de correlación
plt.figure(figsize=(10, 8))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', fmt=".2f")
plt.title('Matriz de Correlación entre Características genero garage')
plt.show()
Matriz de correlación género k-pop¶
# Filtrar el DataFrame por género (por ejemplo, k-pop)
garage_df = df[df['track_genre'] == 'k-pop']
# Calcular la matriz de correlación para el género "k-pop"
correlation_matrix = garage_df[['danceability', 'energy', 'speechiness', 'acousticness', 'instrumentalness', 'liveness', 'valence', 'tempo']].corr()
# Crear un mapa de calor para visualizar la matriz de correlación
plt.figure(figsize=(10, 8))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', fmt=".2f")
plt.title('Matriz de Correlación entre Características genero k-pop')
plt.show()
Matriz de correlación género emo¶
# Filtrar el DataFrame por género (por ejemplo, emo)
garage_df = df[df['track_genre'] == 'emo']
# Calcular la matriz de correlación para el género emo"
correlation_matrix = garage_df[['danceability', 'energy', 'speechiness', 'acousticness', 'instrumentalness', 'liveness', 'valence', 'tempo']].corr()
# Crear un mapa de calor para visualizar la matriz de correlación
plt.figure(figsize=(10, 8))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', fmt=".2f")
plt.title('Matriz de Correlación entre Características genero emo')
plt.show()
Matriz de correlación género indie-pop¶
# Filtrar el DataFrame por género (por ejemplo, indie-pop)
garage_df = df[df['track_genre'] == 'indie-pop']
# Calcular la matriz de correlación para el género "indie-pop"
correlation_matrix = garage_df[['danceability', 'energy', 'speechiness', 'acousticness', 'instrumentalness', 'liveness', 'valence', 'tempo']].corr()
# Crear un mapa de calor para visualizar la matriz de correlación
plt.figure(figsize=(10, 8))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', fmt=".2f")
plt.title('Matriz de Correlación entre Características genero indie-pop')
plt.show()
Matriz de correlación género piano¶
# Filtrar el DataFrame por género (por ejemplo, piano)
garage_df = df[df['track_genre'] == 'piano']
# Calcular la matriz de correlación para el género "piano"
correlation_matrix = garage_df[['danceability', 'energy', 'speechiness', 'acousticness', 'instrumentalness', 'liveness', 'valence', 'tempo']].corr()
# Crear un mapa de calor para visualizar la matriz de correlación
plt.figure(figsize=(10, 8))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', fmt=".2f")
plt.title('Matriz de Correlación entre Características genero piano')
plt.show()
Matriz de correlación género hard-rock¶
# Filtrar el DataFrame por género (por ejemplo, hard-rock)
garage_df = df[df['track_genre'] == 'hard-rock']
# Calcular la matriz de correlación para el género "hard-rock"
correlation_matrix = garage_df[['danceability', 'energy', 'speechiness', 'acousticness', 'instrumentalness', 'liveness', 'valence', 'tempo']].corr()
# Crear un mapa de calor para visualizar la matriz de correlación
plt.figure(figsize=(10, 8))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', fmt=".2f")
plt.title('Matriz de Correlación entre Características genero hard-rock')
plt.show()
Matriz de correlación género british¶
# Filtrar el DataFrame por género (por ejemplo, british)
garage_df = df[df['track_genre'] == 'british']
# Calcular la matriz de correlación para el género "british"
correlation_matrix = garage_df[['danceability', 'energy', 'speechiness', 'acousticness', 'instrumentalness', 'liveness', 'valence', 'tempo']].corr()
# Crear un mapa de calor para visualizar la matriz de correlación
plt.figure(figsize=(10, 8))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', fmt=".2f")
plt.title('Matriz de Correlación entre Características genero british')
plt.show()
Matriz de correlación género soul¶
# Filtrar el DataFrame por género (por ejemplo, soul)
garage_df = df[df['track_genre'] == 'soul']
# Calcular la matriz de correlación para el género "soul"
correlation_matrix = garage_df[['danceability', 'energy', 'speechiness', 'acousticness', 'instrumentalness', 'liveness', 'valence', 'tempo']].corr()
# Crear un mapa de calor para visualizar la matriz de correlación
plt.figure(figsize=(10, 8))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', fmt=".2f")
plt.title('Matriz de Correlación entre Características genero soul')
plt.show()
Matriz de correlación género progressive-house¶
# Filtrar el DataFrame por género (por ejemplo, progressive-house)
garage_df = df[df['track_genre'] == 'progressive-house']
# Calcular la matriz de correlación para el género "progressive-house"
correlation_matrix = garage_df[['danceability', 'energy', 'speechiness', 'acousticness', 'instrumentalness', 'liveness', 'valence', 'tempo']].corr()
# Crear un mapa de calor para visualizar la matriz de correlación
plt.figure(figsize=(10, 8))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', fmt=".2f")
plt.title('Matriz de Correlación entre Características genero progressive-house')
plt.show()
Matriz de correlación género singer-songwriter¶
# Filtrar el DataFrame por género (por ejemplo, singer-songwriter)
garage_df = df[df['track_genre'] == 'singer-songwriter']
# Calcular la matriz de correlación para el género "singer-songwriter"
correlation_matrix = garage_df[['danceability', 'energy', 'speechiness', 'acousticness', 'instrumentalness', 'liveness', 'valence', 'tempo']].corr()
# Crear un mapa de calor para visualizar la matriz de correlación
plt.figure(figsize=(10, 8))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', fmt=".2f")
plt.title('Matriz de Correlación entre Características genero singer-songwriter')
plt.show()
Matriz de correlación género country¶
# Filtrar el DataFrame por género (por ejemplo, country)
garage_df = df[df['track_genre'] == 'country']
# Calcular la matriz de correlación para el género "country"
correlation_matrix = garage_df[['danceability', 'energy', 'speechiness', 'acousticness', 'instrumentalness', 'liveness', 'valence', 'tempo']].corr()
# Crear un mapa de calor para visualizar la matriz de correlación
plt.figure(figsize=(10, 8))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', fmt=".2f")
plt.title('Matriz de Correlación entre Características genero country')
plt.show()
Matriz de correlación género songwriter¶
# Filtrar el DataFrame por género (por ejemplo, songwriter)
garage_df = df[df['track_genre'] == 'songwriter']
# Calcular la matriz de correlación para el género "songwriter"
correlation_matrix = garage_df[['danceability', 'energy', 'speechiness', 'acousticness', 'instrumentalness', 'liveness', 'valence', 'tempo']].corr()
# Crear un mapa de calor para visualizar la matriz de correlación
plt.figure(figsize=(10, 8))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', fmt=".2f")
plt.title('Matriz de Correlación entre Características genero songwriter')
plt.show()
Matriz de correlación género funk¶
# Filtrar el DataFrame por género (por ejemplo, funk)
garage_df = df[df['track_genre'] == 'funk']
# Calcular la matriz de correlación para el género "funk"
correlation_matrix = garage_df[['danceability', 'energy', 'speechiness', 'acousticness', 'instrumentalness', 'liveness', 'valence', 'tempo']].corr()
# Crear un mapa de calor para visualizar la matriz de correlación
plt.figure(figsize=(10, 8))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', fmt=".2f")
plt.title('Matriz de Correlación entre Características genero funk')
plt.show()
Matriz de correlación género spanish¶
# Filtrar el DataFrame por género (por ejemplo, spanish)
garage_df = df[df['track_genre'] == 'spanish']
# Calcular la matriz de correlación para el género "spanish"
correlation_matrix = garage_df[['danceability', 'energy', 'speechiness', 'acousticness', 'instrumentalness', 'liveness', 'valence', 'tempo']].corr()
# Crear un mapa de calor para visualizar la matriz de correlación
plt.figure(figsize=(10, 8))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', fmt=".2f")
plt.title('Matriz de Correlación entre Características genero spanish')
plt.show()
Matriz de correlación género indian¶
# Filtrar el DataFrame por género (por ejemplo, indian)
garage_df = df[df['track_genre'] == 'indian']
# Calcular la matriz de correlación para el género "indian"
correlation_matrix = garage_df[['danceability', 'energy', 'speechiness', 'acousticness', 'instrumentalness', 'liveness', 'valence', 'tempo']].corr()
# Crear un mapa de calor para visualizar la matriz de correlación
plt.figure(figsize=(10, 8))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', fmt=".2f")
plt.title('Matriz de Correlación entre Características genero indian')
plt.show()
Cantidad de canciones explicitas y no explicitas por género¶
# Promedio de popularidad para cada género
avg_popularity = df.groupby('track_genre')['popularity'].mean()
# Top 20 géneros de acuerdo a la popularidad
top_genres = avg_popularity.sort_values(ascending=False).head(114).index
# Filtrar el dataframe
filtered_df = df[df['track_genre'].isin(top_genres)]
# Agrupar por genero y clasificación en explicit
grouped = filtered_df.groupby(['track_genre', 'explicit']).size().unstack(fill_value=0)
# Aplicar logaritmo en base 'e' a los valores para normalizar la cantidad
grouped_log = np.log(grouped + 1)
#print(grouped_log)
#print(grouped_log.columns)
#df_sorted = grouped_log[grouped_log['Index'] == True].sort_values(by='explicit')
# Crear gráfico
plt.rcParams["figure.figsize"] = (20, 12)
grouped_log.plot(kind='bar', color=['blue', 'red'], width=0.5)
plt.xlabel('Género musical', fontsize=16)
plt.ylabel('Cantidad de Canciones (log e)', fontsize=16)
plt.title('Canciones explícitas y no explícitas en log_e para los top 20 géneros musicales más populares')
plt.legend(['No Explícitas', 'Explícitas'], loc='upper right')
plt.show()
Duración promedio de las canciones por género¶
# Agrupar por género de la canción y calcular el promedio de duración
avg_duration_by_genre = df.groupby('track_genre')['duration_ms'].mean().reset_index()
avg_duration_by_genre = avg_duration_by_genre.sort_values(by='duration_ms')
# Crear el gráfico de barras
plt.figure(figsize=(24, 16))
plt.bar(avg_duration_by_genre['track_genre'], avg_duration_by_genre['duration_ms'], color='green')
plt.title('Promedio de Duración por Género de la Canción')
plt.xlabel('Género de la Canción')
plt.ylabel('Duración Promedio (ms)')
plt.xticks(rotation=90, ha='right', fontsize=16) # Rotar las etiquetas del eje x para mayor claridad
plt.grid(axis='y')
plt.show()
Análisis mediante funcionalidad describe()¶
df.describe()
| Unnamed: 0 | popularity | duration_ms | danceability | energy | key | loudness | mode | speechiness | acousticness | instrumentalness | liveness | valence | tempo | time_signature | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| count | 114000.000000 | 114000.000000 | 1.140000e+05 | 114000.000000 | 114000.000000 | 114000.000000 | 114000.000000 | 114000.000000 | 114000.000000 | 114000.000000 | 114000.000000 | 114000.000000 | 114000.000000 | 114000.000000 | 114000.000000 |
| mean | 56999.500000 | 33.238535 | 2.280292e+05 | 0.566800 | 0.641383 | 5.309140 | -8.258960 | 0.637553 | 0.084652 | 0.314910 | 0.156050 | 0.213553 | 0.474068 | 122.147837 | 3.904035 |
| std | 32909.109681 | 22.305078 | 1.072977e+05 | 0.173542 | 0.251529 | 3.559987 | 5.029337 | 0.480709 | 0.105732 | 0.332523 | 0.309555 | 0.190378 | 0.259261 | 29.978197 | 0.432621 |
| min | 0.000000 | 0.000000 | 0.000000e+00 | 0.000000 | 0.000000 | 0.000000 | -49.531000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
| 25% | 28499.750000 | 17.000000 | 1.740660e+05 | 0.456000 | 0.472000 | 2.000000 | -10.013000 | 0.000000 | 0.035900 | 0.016900 | 0.000000 | 0.098000 | 0.260000 | 99.218750 | 4.000000 |
| 50% | 56999.500000 | 35.000000 | 2.129060e+05 | 0.580000 | 0.685000 | 5.000000 | -7.004000 | 1.000000 | 0.048900 | 0.169000 | 0.000042 | 0.132000 | 0.464000 | 122.017000 | 4.000000 |
| 75% | 85499.250000 | 50.000000 | 2.615060e+05 | 0.695000 | 0.854000 | 8.000000 | -5.003000 | 1.000000 | 0.084500 | 0.598000 | 0.049000 | 0.273000 | 0.683000 | 140.071000 | 4.000000 |
| max | 113999.000000 | 100.000000 | 5.237295e+06 | 0.985000 | 1.000000 | 11.000000 | 4.532000 | 1.000000 | 0.965000 | 0.996000 | 1.000000 | 1.000000 | 0.995000 | 243.372000 | 5.000000 |
Contribución de miembros Hito 1¶
| Tarea | Encargado/a |
|---|---|
| Trabajo en la Presentación | Scarlett Plaza |
| Vicente Thiele | |
| Trabajo en el Informe | |
| Javiera Romero | |
| Patricio Espinoza | |
| Rodrigo Díaz | |
| Revisión Problemas | |
| Rodrigo Díaz | |
| Vicente Thiele | |
| Scarlett Plaza | |
| Gráficos de Matrices de Correlación por género | |
| Javiera Romero | |
| Scarlett Plaza | |
| Gráfico Cantidad de Aparición de los Géneros en las 300 Canciones más Populares | |
| Javiera Romero | |
| Gráfico Popularidad Promedio por Género | |
| Rodrigo Díaz | |
| Gráfico Cantidad de Canciones por Género | |
| Rodrigo Díaz | |
| Gráfico Género por Explícito | |
| Patricio Espinoza | |
| Gráfico de Duración Promedio por Género | |
| Patricio Espinoza | |
| Análisis mediante .describe() | |
| Patricio Espinoza |
Mejoras Hito 1¶
Retroalimentación: "Seria bien de ver una visualizacion de los generos con los una agregacion de embeddings. Porque no usaron el genero en las correlaciones? Demasiado matrices de correlaciones, no concluision"
Decisiones tomadas:
Se realizó una reducción de las matrices de correlación, pasando de muchas matrices a una matriz que representa las similitudes entre géneros.
Se agrega una matriz de correlación general, con las caacteristicas musicales y los géneros.
Análisis de metricas de distancia y similitud entre géneros para considerarlo en el pre-procesamiento de las metdodologías del Hito 2.
Conclusión Hito 1
Mejorar fase exploratoria¶
Matriz de correlacion entre generos, muestra similitud entre generos¶
import matplotlib.pyplot as plt
import numpy as np
from scipy.spatial.distance import pdist, squareform
genres_of_interest = ['pop', 'rock', 'jazz', 'dance', 'latino', 'reggaeton', 'hip-hop', 'reggae', 'edm', 'latin', 'electro', 'indie', 'alternative', 'house', 'alt-rock', 'garage','k-pop', 'emo', 'indie-pop', 'piano', 'hard-rock', 'british', 'soul', 'singer-songwriter','country', 'songwriter']
correlation_matrices = {}
for genre in genres_of_interest:
genre_df = df[df['track_genre'] == genre]
correlation_matrix = genre_df[['danceability', 'energy', 'speechiness', 'acousticness',
'instrumentalness', 'liveness', 'valence', 'tempo']].corr()
correlation_matrices[genre] = correlation_matrix
def get_upper_triangular(matrix):
return matrix.where(np.triu(np.ones(matrix.shape), k=1).astype(bool)).stack()
correlation_vectors = {genre: get_upper_triangular(matrix) for genre, matrix in correlation_matrices.items()}
correlation_df = pd.DataFrame(correlation_vectors).T
distances = pdist(correlation_df, metric='euclidean')
distance_matrix = squareform(distances)
similarity_matrix = 1 / (1 + distance_matrix)
plt.figure(figsize=(20, 10))
sns.heatmap(similarity_matrix, annot=True, xticklabels=genres_of_interest, yticklabels=genres_of_interest, cmap='coolwarm', fmt=".2f")
plt.title('Matriz de Similitud entre Matrices de Correlación de Géneros')
plt.show()
# Convertir la matriz de similitud en una matriz de distancia
distance_matrix = 1 - similarity_matrix
# Convertir la matriz de distancia en un DataFrame para facilitar el manejo
distance_df = pd.DataFrame(distance_matrix, index=genres_of_interest, columns=genres_of_interest)
# Obtener los 5 valores más altos en la matriz de distancia (disimilitudes más grandes)
# Obtenemos la matriz triangular superior para evitar duplicados y autocomparaciones
upper_triangular = distance_df.where(np.triu(np.ones(distance_df.shape), k=1).astype(bool))
# Transformar la matriz triangular superior a una serie ordenada por los valores de distancia
sorted_disimilarities = upper_triangular.stack().sort_values(ascending=False)
# Seleccionar los 5 géneros más distintos entre sí
top_5_disimilar_genres = sorted_disimilarities.head(5)
print(top_5_disimilar_genres)
# Mostrar los pares de géneros más distintos y sus valores de disimilitud
for (genre1, genre2), dissimilarity in top_5_disimilar_genres.items():
print(f"{genre1} y {genre2} son muy distintos con una disimilitud de {dissimilarity:.2f}")
edm piano 0.674763 reggaeton piano 0.674242 reggae piano 0.670710 latino piano 0.668328 house piano 0.664965 dtype: float64 edm y piano son muy distintos con una disimilitud de 0.67 reggaeton y piano son muy distintos con una disimilitud de 0.67 reggae y piano son muy distintos con una disimilitud de 0.67 latino y piano son muy distintos con una disimilitud de 0.67 house y piano son muy distintos con una disimilitud de 0.66
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
from scipy.spatial.distance import pdist, squareform
genre_popularity = df.groupby('track_genre')['popularity'].mean()
top_50_genres = genre_popularity.sort_values(ascending=False).head(50).index
#Filtrar el DataFrame para incluir solo los 50 géneros más populares
df_50_genres = df[df['track_genre'].isin(top_50_genres)]
#Crear un diccionario para almacenar las matrices de correlación
correlation_matrices = {}
for genre in top_50_genres:
genre_df = df_50_genres[df_50_genres['track_genre'] == genre]
correlation_matrix = genre_df[['danceability', 'energy', 'speechiness', 'acousticness',
'instrumentalness', 'liveness', 'valence', 'tempo']].corr()
correlation_matrices[genre] = correlation_matrix
#Función para obtener la parte triangular superior de la matriz
def get_upper_triangular(matrix):
return matrix.where(np.triu(np.ones(matrix.shape), k=1).astype(bool)).stack()
#Obtener los vectores de correlación para cada género
correlation_vectors = {genre: get_upper_triangular(matrix) for genre, matrix in correlation_matrices.items()}
# Convertir el diccionario en un DataFrame
correlation_df = pd.DataFrame(correlation_vectors).T
# Calcular la matriz de distancias
distances = pdist(correlation_df, metric='euclidean')
distance_matrix = squareform(distances)
# Convertir las distancias en similitudes
similarity_matrix = 1 / (1 + distance_matrix)
# Visualizar la matriz de similitud
plt.figure(figsize=(40, 20))
sns.heatmap(similarity_matrix, annot=True, xticklabels=top_50_genres, yticklabels=top_50_genres, cmap='coolwarm', fmt=".2f")
plt.title('Matriz de Similitud entre Matrices de Correlación de Géneros')
plt.show()
Matriz de correlacion incluyendo los generos¶
#Lo haremos con algunos generos de interes
top_20_genres = ['pop', 'rock', 'jazz', 'dance', 'latino', 'reggaeton', 'hip-hop', 'reggae', 'edm', 'latin', 'electro', 'indie', 'alternative', 'house', 'alt-rock', 'garage','k-pop', 'emo', 'indie-pop', 'piano', 'hard-rock']
#Filtramos
df_top_20 = df[df['track_genre'].isin(top_20_genres)]
#Hacemos los generos una variable dummy
df_dummies = pd.get_dummies(df_top_20['track_genre'], prefix='genre')
#Combinamos
df_combined = pd.concat([df_top_20, df_dummies], axis=1)
columns_of_interest = ['danceability', 'energy', 'loudness', 'mode', 'speechiness',
'acousticness', 'instrumentalness', 'liveness', 'valence',
'tempo'] + list(df_dummies.columns)
df_numerical = df_combined[columns_of_interest]
#Matriz de correlación
correlation_matrix = df_numerical.corr()
plt.figure(figsize=(20, 15))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm')
<Axes: >
Loudness promedio por género¶
# Agrupa los datos por género y calcula la media de loudness
loudness_df = df.groupby('track_genre')['loudness'].mean().reset_index()
# Gráfico de loudness vs género
plt.figure(figsize=(24, 16))
plt.bar(loudness_df['track_genre'], loudness_df['loudness'])
plt.xlabel('Género')
plt.ylabel('Loudness (dB)')
plt.title('Loudness vs Género')
plt.xticks(rotation=90, ha='right', fontsize=16)
plt.tight_layout()
plt.show()
Keys por género¶
# Agrupa los datos por género y cuenta la frecuencia de keys
key_counts_df = df.groupby('track_genre')['key'].value_counts().unstack(fill_value=0)
# Reorganiza el DataFrame para tener una fila por género
key_counts_df = key_counts_df.reset_index()
# Define los colores para cada barra en el gráfico
colors = plt.cm.tab20.colors[:len(key_counts_df.columns) - 1]
# Gráfico de frecuencia de keys por género
plt.figure(figsize=(24, 16))
for i, key in enumerate(key_counts_df.columns[1:]): # Comienza desde la segunda columna (la primera es 'track_genre')
plt.bar(key_counts_df['track_genre'], key_counts_df[key], label=key, color=colors[i])
plt.xlabel('Género')
plt.ylabel('Frecuencia de Keys')
plt.title('Frecuencia de Keys por Género')
plt.xticks(rotation=90, ha='right', fontsize=16)
plt.legend(title='Key', bbox_to_anchor=(1, 1), loc='upper left')
plt.tight_layout()
plt.show()
Mode por género¶
# Agrupa los datos por género y cuenta la frecuencia de mode
mode_counts_df = df.groupby('track_genre')['mode'].value_counts().unstack(fill_value=0)
# Reorganiza el DataFrame para tener una fila por género
mode_counts_df = mode_counts_df.reset_index()
# Define los colores para cada barra en el gráfico
colors = plt.cm.tab20.colors[:len(mode_counts_df.columns) - 1]
# Gráfico de frecuencia de mode por género
plt.figure(figsize=(24, 16))
for i, mode in enumerate(mode_counts_df.columns[1:]): # Comienza desde la segunda columna (la primera es 'track_genre')
plt.bar(mode_counts_df['track_genre'], mode_counts_df[mode], label=mode, color=colors[i])
plt.xlabel('Género')
plt.ylabel('Frecuencia de Mode')
plt.title('Frecuencia de Mode por Género')
plt.xticks(rotation=90, ha='right', fontsize=16)
plt.legend(title='Mode', bbox_to_anchor=(1, 1), loc='upper left')
plt.tight_layout()
plt.show()
Time_signature por género¶
# Agrupa los datos por género y cuenta la frecuencia de time_signature
time_signature_counts_df = df.groupby('track_genre')['time_signature'].value_counts().unstack(fill_value=0)
# Reorganiza el DataFrame para tener una fila por género
time_signature_counts_df = time_signature_counts_df.reset_index()
# Define los colores para cada barra en el gráfico
colors = plt.cm.tab20.colors[:len(time_signature_counts_df.columns) - 1]
# Gráfico de frecuencia de time_signature por género
plt.figure(figsize=(24, 16))
for i, time_signature in enumerate(time_signature_counts_df.columns[1:]): # Comienza desde la segunda columna (la primera es 'track_genre')
plt.bar(time_signature_counts_df['track_genre'], time_signature_counts_df[time_signature], label=time_signature, color=colors[i])
plt.xlabel('Género')
plt.ylabel('Frecuencia de Time Signature')
plt.title('Frecuencia de Time Signature por Género')
plt.xticks(rotation=90, ha='right', fontsize=16)
plt.legend(title='Time Signature', bbox_to_anchor=(1, 1), loc='upper left')
plt.tight_layout()
plt.show()
df['time_signature'].value_counts()
time_signature 4 101843 3 9195 5 1826 1 973 0 163 Name: count, dtype: int64
Conclusión Hito 1¶
En base a los gráficos de Mode y Time_signature podemos concluir que no son atributos que permitan clasificar a que género se pertenece.
Mode: La mayoría de los géneros tienen Mode tipo 1, lo que significa que no será un atributo musical que permita distinguir claramente a los géneros, a excepción de "sad" o "turkish"
Time_signature: Al igual que para Mode, la mayoría de los géneros tienen principalmente Time_signature 4, lo que significa que no será un atributo musical que permita distinguir al momento de clasificar por géneros.
Por otro lado, duration_ms es un atributo musical que permitirá distinguir con claridad algunos géneros, es por ello que para el Hito 2 se debe evaluar si dentro de los géneros con los que se trabajará se encuentran aquellos distinguibles por este atributo.
Para 'explicit' ocurre similar, hay géneros en que la mayoría de canciones son no explicitas, otros en los que hay igual cantidad de explicitas y no explicitas, y aquellos que tienen más explicitas que no explicitas. Por ello, será importante evaluar que tanto se sigue cumpliendo una vez que se reduzca la cantidad de géneros.
Los atributos utilizados en las matrices de correlación (danceability, energy, loudness, speechiness, acousticness, instrumentalness, liveness, valence y tempo) mostrarón diferentes valores dependiendo de cada género. Por ello, se tendrán en cuenta al momento de clasificar géneros, y en caso de que el modelo no sea óptimo se evaluará eliminar los atributos menos relevantes para distinguir entre géneros, como es el caso de 'keys' en donde todos los géneros tienen gran cantidad de canciones con key igual a 11.
Informe Hito 2¶
2. Propuesta metodológica experimental inicial:¶
Preguntas:
(1) ¿Es posible predecir el género al que pertenece una canción de acuerdo a las variables musicales tales como el tempo, la valencia musical, cantidad de beats, entre otros?
Metodología:
Se tienen las opciones de clustering, regresión y clasificación, entre estas se optará por la clasificación, ya que nos permitirá responder directamente la pregunta sobre si es posible predecir el género al que pertenece una canción dados los atributos musicales, el tipo de clasificación a realizar será multiclase (cada instancia pertenece a una sola y única clase/etiqueta).
La elección de este método radica en que se desea predecir una categoría, i.e. etiqueta/clase, en base a otros atributos de la instancia, esto puede resultar ventajoso en una clara interpretabilidad usando árboles de decisiones o SVM.
Por otro lado, la regresión no es útil ya que no se desea predecir un valor continuo sino que una etiqueta. Asimismo, clustering tampoco es de utilidad, ya que solo nos sirve para observar, descubrir e identificar patrones mediante la agrupación de datos.
Las etapas a desarrollar consisten en:
- Preprocesamiento en los datos
- División en los conjuntos Test y Training
- Resampling para el conjunto de Training
- Implementación de modelos
- Evaluación de modelos
- Conclusión (Modelo seleccionado y argumentos)
Preprocesamiento
Para darle respuesta a este problema primero se tendrá que realizar una reducción de géneros (columna track genre) del dataset basado en los 10 más populares, esto con tal de centrar el modelo clasificatorio en dichas etiquetas. A esta reducción se le sumará la aplicación de un Merge de géneros, juntando aquellos que se asemejan más en base a los atributos danceability, energy, loudness, speechiness, acousticness, instrumentalness, liveness, valence y tempo de las instancias pertenecientes a la etiqueta. Para ello se tendrá en cuenta métricas de distancia como distancia euclidiana , y distancia Manhattan junto con métricas de similitud como similitud coseno y coeficiente de correlación de Pearson.
Además de la reducción de etiquetas, se eliminarán las columnas que no entreguen información relevante para la clasificación del género musical: 'Unnamed: 0', 'track_id', 'artists', 'album_name', 'track_name', ‘popularity’ Esto con el fin de reducir dimensiones y evitar que la maldición de la dimensionalidad afecte a las métricas y al modelo.
Con respecto a las métricas de distancia y similitud, se aplicará una estandarización de los datos con el fin de mejorar la precisión de los cálculos.
A priori similitud coseno nos será útil para determinar si la orientación de los vectores es similar, para la magnitud esta métrica pierde relevancia por lo que se utilizará el coeficiente de correlación de Pearson para ello, esto nos permitirá observar tanto la orientación como la magnitud entre las relaciones de cada clase normalizando los valores.
Por el lado de las métricas de distancia, tenemos que la distancia euclidiana proporciona una interpretación clara de la similitud (valor más bajo indica mayor similitud), sin embargo se puede ver afectada por la maldición de la dimensionalidad. Además, para compensar la sensibilidad de la distancia euclidiana a los outliers se implementará la distancia de Manhattan, proporcionando así un análisis más exhaustivo para determinar correctamente qué géneros se pueden combinar.
División en los conjuntos Test y Training
Las particiones que se tomarán para cada conjunto serán 0.80 para Training y 0.20 para Testing. Esta decisión se basa en que cada clase contiene 1.000 instancias, y por ende una variedad de valores en los atributos musicales que debería permitirle al modelo generalizar correctamente. Estas particiones pueden ser actualizadas en la fase de Evaluación del modelo en caso de que el modelo no tenga la capacidad de generalizar correctamente, sin embargo, se realizarán técnicas de resampling, por lo que lo esperado es no modificar estas particiones y que esta técnica compense correctamente el balance de las clases.. Los atributos musicales corresponderán a las features (X), mientras que los géneros a las labels (Y), obteniéndose los conjuntos X_test, X_train, Y_test e Y_train.
Resampling
Dentro de la metodología se menciona que se realizará una combinación de géneros, por lo que se espera que se pierda el equilibrio entre la cantidad de instancias por clase, en base a ello se realizará Random Oversampling en las clases minoritarias y Random Subsampling en aquellas mayoritarias. Con esto se espera que se recupere el equilibrio en el conjunto de datos, además de que la precisión del modelo aumente al reducir el sesgo con Subsampling y se reduzca el overfitting, obteniendo mejores métricas al evaluar el modelo.
Implementación de modelos
Teniendo todo lo anterior listo nos enfocaremos en encontrar el clasificador que se adapte más a nuestro modelo. Para esto utilizaremos técnicas de aprendizaje supervisado, como lo son el modelo Gaussiano de Bayes en el que se tienen variables independientes y es eficaz al trabajar en problemas de multiclase, SVM pues es más potente, y debería poseer mayor precisión que bayes al maximizar el margen entre clases, además de ser robusto ante el overfitting y Gradient Boosting Machine ya que captura relaciones no lineales, tiene alta precisión en clasificación, permite el uso de funciones de pérdida, y también posee robustez ante overfitting. Además, dependiendo del modelo se evaluará la opción de utilizar la función GridSearchCV para complementar la búsqueda de los mejores hiperparametros.
Evaluación de modelos
Las métricas a considerar serán accuracy y precision, con énfasis en precision ya que nos importa que las instancias clasificadas sean correctas. Es decir, queremos reducir los falsos positivos, de forma que las canciones etiquetadas para una clase, sean realmente de dicha clase.
Conclusión
Se concluirá cuál fue el mejor modelo argumentando en base al desempeño y se dará feedback respecto a la metodología empleada.
(2) ¿Existen géneros que, pese a ser distintos, sus canciones tienen atributos similares? ¿Y viceversa?
Metodología:
Para responder esta pregunta se aplicarán técnicas de clustering debido a que se busca encontrar patrones de similitud entre los grupos de datos.
Preprocesamiento
Para empezar se hará un preprocesamiento similar al del método 1, donde se eliminarán columnas que no aportan información relevante en relación con los atributos musicales, estás corresponden a las columnas “track_id", “popularity”, “album_name”, “artists”, “duration_ms”, “track_name” y “Unnamed: 0” en este caso no se harán merge de géneros como en el método anterior, ya que de hacerlo podría verse afectado el resultado del método. Dado que los datos cuentan con muchos géneros musicales se elegirán 10 para hacer el proceso más simple, dichos géneros serán elegidos tal que podamos visualizar tanto casos donde canciones de distintos géneros no tienen características parecidas como cuando si.
Además dado que los datos sobre las características musicales (valence, tempo, loudness, etc) están evaluados en rangos numéricos distintos, a estos se les aplicará un escalamiento tal que influyan de manera equitativa en el cálculo posterior de clusters. Por último se definirán los atributos relevantes para definir la similitud entre canciones, estos corresponden a “explicit”, “danceability”, “energy”, “key”, “loudness”, “mode”, “speechiness”, “acousticness”, “instrumentalness”, “liveness”, “valence”, “tempo” y “time_signature” dado que son los atributos que más caracterizan a una canción de otra en contexto del género al que pertenecen.
Implementación y evaluación de los modelos XXXX Silhouete considera cohesion y separacion
Las técnicas específicas de clustering que se utilizaran son K-means y DBSCAN, K-means nos servirá gracias a su simpleza mientras que DBSCAN nos permitirá manejar ruido de manera más certera. Dado que en nuestro caso sabemos las cantidades de clusters que queremos, que corresponde a la cantidad de géneros que estamos analizando para luego ver el solapamiento entre clusters, no se utilizarán técnicas como el método del codo o Silhoutte. Luego de aplicar ambos métodos, estos serán comparados y nos quedaremos con el que dé mejores resultados, para evaluar su rendimiento en el caso de K-means se utilizará medirán los coeficientes de cohesión y separación de forma independiente para cada cluster ya que se trabajará con una cantidad reducida de estos, y para la comparación de ambos métodos utilizaremos Matriz de similitud y Coeficiente de Silhoutte esto dado que, ya que se utilizará DBSCAN, la matriz de similitud puede no darnos las mejores conclusiones por sí sola.
Visualización de clusters y conclusión
Finalmente para la visualización de los datos se creará una columna en el data frame que contenga el atributo labels_ para poder así visualizar en base a esto y los géneros. Se reducirán dimensiones utilizando técnicas de PCA para así poder contar con una mejor visualización de los resultados al ayudarnos a mantener una buena varianza y pocas dimensiones logrando obtener resultados visuales más fáciles de interpretar. Para poder ver la distribución de géneros en cada cluster utilizaremos gráficos de barra que nos mostrarán la proporción de canciones pertenecientes a cada género en cada cluster.
(3) ¿Los géneros con más canciones explícitas comparten características entre ellos?
Metodología:
Dado que la pregunta tiene un carácter similar a la anterior, es decir, se buscan similitudes entre grupos de datos, las técnicas a utilizar serán de clustering.
Preprocesamiento
Al igual que en las preguntas anteriores, en el dataset original hay atributos que no son necesarios para responder la pregunta y que solo nos molestarán en el desarrollo de los modelos. En este caso, como se desean comparar características musicales para obtener una respuesta, las columnas que se eliminarán corresponden a aquellos atributos que no son imprescindibles al crear una canción como lo son “Unnamed: 0”, “track_id”, “artists”, “album_name”, “track_name”, “popularity” y “duration_ms”.
En cuanto a los atributos que nos interesan, dado que los valores de “key”, “loudness”, “tempo” y “time_signature” se mueven por rangos diferentes a los demás, estos serán normalizados a rango entre 0 y 1, como la mayoría de atributos, para así evitar posibles errores en el entrenamiento del modelo. Por ejemplo, para el método K-means, al haber columnas con un rango muy alto puede que estas generen una dominancia por sobre las demás debido a que en el cálculo de la distancia el valor que más afecta será el mayor haciendo insignificante al atributo de mucho menor valor, lo cual también generaría un sesgo en nuestra respuesta.
Por último, solo se escogerán solo las instancias del dataset cuyo “track_genre” este en los 10 géneros con más canciones explícitas de todo el dataset, ya que la pregunta hace mención justamente a este tipo de géneros, además, serán 10 los escogidos porque será más fácil trabajar con esa cantidad y generar un modelo más correcto aun teniendo una cantidad de géneros considerables.
Implementación y evaluación de los modelos
Las técnicas específicas de clustering a utilizar serán las mencionadas en la pregunta 2, K-means por su simpleza y DBSCAN por su manejo de ruido, estas además se aplicarán de forma muy parecida a la pregunta 2, pues como se mencionó antes, ambas preguntas son de carácter similar, ya que el único cambio que existe entre cada pregunta es que en una se trabaja con 10 géneros cualquiera (pregunta 2) y en la otra son los 10 géneros con más canciones explícitas en el dataset (pregunta 3). Entonces luego de realizar todo el proceso mencionado en la pregunta 2 (es importante recordar que la cantidad de cluster que queremos, corresponde a la cantidad de géneros que estamos analizando para luego ver el solapamiento entre clusters).
Conclusión
Finalmente, será gracias al gráfico de barras que podremos contestar la pregunta y ver como los cluster son formados por un género o por una mezcla de géneros haciendo ver que los géneros con más canciones explícitas en el dataset no comparten características o si lo hacen, respectivamente.
3. Resultado preliminar¶
Importar librerias necesarias¶
import pandas as pd
import numpy as np
# Metricas de similitud
from scipy.spatial.distance import euclidean, cityblock
from scipy.stats import pearsonr
from sklearn.metrics.pairwise import cosine_similarity
# Graficos
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# Procesamiento
from sklearn.preprocessing import StandardScaler, OneHotEncoder # Estandarizacion
from sklearn.preprocessing import MinMaxScaler # Normalizacion
from sklearn.decomposition import PCA
# Split
from sklearn.model_selection import train_test_split
# Sampling
from imblearn.over_sampling import RandomOverSampler
from imblearn.under_sampling import RandomUnderSampler
# Models
from sklearn import svm
from sklearn.svm import SVC # Support Vector Machine
from sklearn.ensemble import GradientBoostingClassifier # GradientBoost
from sklearn.naive_bayes import GaussianNB # Bayes
# Metrics
from sklearn.metrics import classification_report
from sklearn.metrics import make_scorer, precision_score
from sklearn.model_selection import cross_val_score
# GridSearch
from sklearn.model_selection import GridSearchCV
#from sklearn.metrics import confusion_matrix
Pre-procesamiento¶
##### FILTRADO DE GÉNEROS POR POPULARIDAD #####
# Top 10 géneros más populares:
num_genres = 10
# 1. Promedio de popularidad por género
genre_popularity = df.groupby('track_genre')['popularity'].mean()
# 2. 50 género más populares
top_genres = genre_popularity.sort_values(ascending=False).head(num_genres).index
# 3. Creamos el nuevo dataframe
df_top_genres = df[df['track_genre'].isin(top_genres)].copy()
# Eliminar columnas que no aportan información relevante: Unnamed, track_id, artists, album_name, track_name
df_top_genres.drop(['Unnamed: 0', 'track_id', 'artists', 'album_name', 'track_name', 'popularity'], axis='columns', inplace=True)
# Atributos musicales que se tendrán en cuenta
# Eliminados: time_signature, mode, duration_ms, keys, explicit
musical_attributes = df_top_genres[['track_genre', 'danceability', 'energy',
'loudness', 'speechiness', 'acousticness',
'instrumentalness', 'liveness', 'valence', 'tempo']]
print(f"\n *** Se estan considerando los {num_genres} generos mas populares *** \n")
musical_attributes
*** Se estan considerando los 10 generos mas populares ***
| track_genre | danceability | energy | loudness | speechiness | acousticness | instrumentalness | liveness | valence | tempo | |
|---|---|---|---|---|---|---|---|---|---|---|
| 5000 | anime | 0.541 | 0.846 | -2.729 | 0.0551 | 0.01220 | 0.000149 | 0.1630 | 0.524 | 129.138 |
| 5001 | anime | 0.436 | 0.934 | -2.685 | 0.0507 | 0.00001 | 0.140000 | 0.3210 | 0.384 | 91.481 |
| 5002 | anime | 0.577 | 0.941 | -5.170 | 0.1050 | 0.00207 | 0.000003 | 0.0891 | 0.292 | 101.921 |
| 5003 | anime | 0.508 | 0.889 | -2.755 | 0.0862 | 0.04950 | 0.000000 | 0.0984 | 0.332 | 135.014 |
| 5004 | anime | 0.691 | 0.773 | -5.244 | 0.0494 | 0.01740 | 0.000451 | 0.1170 | 0.502 | 128.162 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 97995 | sertanejo | 0.769 | 0.718 | -5.809 | 0.0241 | 0.23400 | 0.000000 | 0.1680 | 0.725 | 105.015 |
| 97996 | sertanejo | 0.529 | 0.564 | -4.713 | 0.0247 | 0.66800 | 0.000000 | 0.1960 | 0.594 | 160.445 |
| 97997 | sertanejo | 0.690 | 0.926 | -5.584 | 0.1830 | 0.33600 | 0.000000 | 0.0319 | 0.867 | 178.119 |
| 97998 | sertanejo | 0.763 | 0.769 | -6.245 | 0.0292 | 0.23500 | 0.000041 | 0.0545 | 0.857 | 92.035 |
| 97999 | sertanejo | 0.771 | 0.722 | -8.718 | 0.0327 | 0.13800 | 0.000000 | 0.1270 | 0.940 | 130.065 |
10000 rows × 10 columns
Metricas de distancia y similitud¶
# Separar los atributos musicales de los géneros
attributes = musical_attributes.drop('track_genre', axis=1)
genres = musical_attributes['track_genre']
# Estandarizar los datos
scaler = StandardScaler()
attributes_scaled = scaler.fit_transform(attributes)
# Crear un DataFrame estandarizado y agregar la columna de géneros de nuevo
musical_attributes_scaled = pd.DataFrame(attributes_scaled, columns=attributes.columns)
musical_attributes_scaled['track_genre'] = genres.values
# Calcular el centroide de cada género mediante el promedio
genre_centroids = musical_attributes_scaled.groupby('track_genre').mean()
# Diccionarios para las distancias eucledianas y manhattan
dicc_euclidean = {}
dicc_manhattan = {}
# Limites
euclid_lim = 1
manhatt_lim = 2
# Diccionarios para similitud coseno y correlacion de Pearson
dicc_cosine = {}
dicc_pearson = {}
# Limites
cosine_lim = 0.7
pear_lim = 0.7
# Generos vistos
dicc_genres = {}
# Calcular las distancias entre todos los generos
for genre1 in genre_centroids.index:
for genre2 in genre_centroids.index:
if genre1 != genre2 and (genre2, genre1) not in dicc_genres:
# Guardar que ya se vio el caso
dicc_genres[(genre1, genre2)] = 1
# Calculo de metricas
distance_euclid = euclidean(genre_centroids.loc[genre1], genre_centroids.loc[genre2])
distance_manhatt = cityblock(genre_centroids.loc[genre1], genre_centroids.loc[genre2])
cosine_sim = cosine_similarity(genre_centroids.loc[genre1].values.reshape(1, -1), genre_centroids.loc[genre2].values.reshape(1, -1))[0][0]
pearson_corr, _ = pearsonr(genre_centroids.loc[genre1], genre_centroids.loc[genre2])
# Distancia euclediana
if 0 < distance_euclid < euclid_lim:
dicc_euclidean[(genre1, genre2)] = distance_euclid
# Distancia manhattan
if 0 < distance_manhatt < manhatt_lim:
dicc_manhattan[(genre1, genre2)] = distance_manhatt
# Similitud coseno
if cosine_lim <= cosine_sim:
dicc_cosine[(genre1, genre2)] = cosine_sim
# Correlacion de Pearson
if pear_lim <= pearson_corr:
dicc_pearson[(genre1, genre2)] = pearson_corr
# Mostrar las distancias calculadas
for pair, distance in dicc_euclidean.items():
print(f"Distancia euclidiana entre {pair[0]} y {pair[1]}: {distance}")
print("\n")
for pair, distance in dicc_manhattan.items():
print(f"Distancia manhattan entre {pair[0]} y {pair[1]}: {distance}")
print("\n")
for pair, distance in dicc_cosine.items():
print(f"Similitud Coseno entre {pair[0]} y {pair[1]}: {distance}")
print("\n")
for pair, distance in dicc_pearson.items():
print(f"Correlación de Pearson entre {pair[0]} y {pair[1]}: {distance}")
print("\n")
Distancia euclidiana entre chill y sad: 0.5848205433948878 Distancia euclidiana entre emo y k-pop: 0.7863926991097293 Distancia euclidiana entre emo y pop: 0.7930656593261709 Distancia euclidiana entre indian y pop: 0.8002791474779437 Distancia euclidiana entre indian y pop-film: 0.48516890326023265 Distancia euclidiana entre k-pop y pop: 0.47668062492395546 Distancia euclidiana entre k-pop y pop-film: 0.8217132662131349 Distancia euclidiana entre pop y pop-film: 0.5148214329997334 Distancia manhattan entre chill y sad: 1.4733272367327162 Distancia manhattan entre emo y k-pop: 1.885545813332767 Distancia manhattan entre indian y pop-film: 1.2062134589628986 Distancia manhattan entre k-pop y pop: 1.1950155609940454 Distancia manhattan entre pop y pop-film: 1.2373181145239005 Similitud Coseno entre chill y sad: 0.9227687466640525 Similitud Coseno entre indian y pop-film: 0.7156395644728744 Similitud Coseno entre k-pop y pop: 0.760057998619438 Correlación de Pearson entre chill y sad: 0.9255625193128174 Correlación de Pearson entre k-pop y pop: 0.8296569830959191
Dataset para el modelo¶
# DATAFRAME ESTANDARIZADO
##### FILTRADO DE GÉNEROS POR POPULARIDAD #####
# Top 10 géneros más populares:
num_genres = 10
# 1. Promedio de popularidad por género
genre_popularity = df.groupby('track_genre')['popularity'].mean()
# 2. Género más populares
top_genres = genre_popularity.sort_values(ascending=False).head(num_genres).index
# 3. Creamos el nuevo dataframe
df_top_genres = df[df['track_genre'].isin(top_genres)].copy()
# Eliminar columnas que no aportan información relevante: Unnamed, track_id, artists, album_name, track_name
df_top_genres.drop(['Unnamed: 0', 'track_id', 'artists', 'album_name', 'track_name', 'popularity'], axis = 'columns', inplace=True)
# Atributos musicales que se tendrán en cuenta
# Eliminados: time_signature, mode, duration_ms, keys, explicit
musical_attributes = df_top_genres[['track_genre', 'danceability', 'energy',
'loudness', 'speechiness', 'acousticness',
'instrumentalness', 'liveness', 'valence', 'tempo']]
# Separar los atributos musicales de los géneros
attributes = musical_attributes.drop('track_genre', axis=1)
genres = musical_attributes['track_genre']
# Estandarizar los datos
scaler = StandardScaler()
attributes_scaled = scaler.fit_transform(attributes)
# Crear un DataFrame estandarizado y agregar la columna de géneros de nuevo
df_model = pd.DataFrame(attributes_scaled, columns=attributes.columns)
df_model['track_genre'] = genres.values
# Merge:
# Combinar con pop
df_model['track_genre'] = df_model['track_genre'].replace('k-pop', 'pop')
df_model['track_genre'] = df_model['track_genre'].replace('indian', 'pop')
df_model['track_genre'] = df_model['track_genre'].replace('pop-film', 'pop')
# Combinar sad con chill
df_model['track_genre'] = df_model['track_genre'].replace('sad', 'chill')
df_model
| danceability | energy | loudness | speechiness | acousticness | instrumentalness | liveness | valence | tempo | track_genre | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | -0.400210 | 1.020159 | 1.264896 | -0.354629 | -1.105335 | -0.316035 | -0.248729 | 0.209662 | 0.251332 | anime |
| 1 | -1.100814 | 1.417516 | 1.276165 | -0.405292 | -1.144952 | 0.334837 | 0.603849 | -0.423663 | -0.988431 | anime |
| 2 | -0.160003 | 1.449123 | 0.639754 | 0.219933 | -1.138258 | -0.316713 | -0.647498 | -0.839848 | -0.644720 | anime |
| 3 | -0.620400 | 1.214322 | 1.258238 | 0.003465 | -0.984107 | -0.316728 | -0.597314 | -0.658898 | 0.444785 | anime |
| 4 | 0.600653 | 0.690533 | 0.620802 | -0.420260 | -1.088434 | -0.314629 | -0.496947 | 0.110140 | 0.219200 | anime |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 9995 | 1.121102 | 0.442185 | 0.476105 | -0.711571 | -0.384470 | -0.316728 | -0.221748 | 1.118936 | -0.542858 | sertanejo |
| 9996 | -0.480279 | -0.253190 | 0.756792 | -0.704663 | 1.026059 | -0.316728 | -0.070659 | 0.526325 | 1.282038 | sertanejo |
| 9997 | 0.593981 | 1.381392 | 0.533728 | 1.118047 | -0.052963 | -0.316728 | -0.956152 | 1.761309 | 1.863910 | sertanejo |
| 9998 | 1.081067 | 0.672471 | 0.364445 | -0.652848 | -0.381220 | -0.316537 | -0.834201 | 1.716071 | -0.970192 | sertanejo |
| 9999 | 1.134447 | 0.460246 | -0.268893 | -0.612548 | -0.696476 | -0.316728 | -0.442987 | 2.091543 | 0.281851 | sertanejo |
10000 rows × 10 columns
# Géneros
df_model['track_genre'].unique()
array(['anime', 'chill', 'emo', 'grunge', 'pop', 'sertanejo'],
dtype=object)
Crear modelos¶
# Codigo General para el Split
### SPLIT ###
# Separar las características y la variable objetivo
X = df_model.drop(columns='track_genre', axis=1)
y = df_model['track_genre']
## 4.000 -> Pop
## 2.000 -> Chill
## 1.000 -> grunge, sertanejo, emo, anime
# Training = 0.80
# Pop -> 3200
# Chill -> 1800
# Los demas -> 800
# Dividir los datos en entrenamiento y prueba
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, random_state=39, stratify=y)
# Agrupar los datos por género y contar la cantidad de canciones en cada grupo
songs_per_genre = df_model.groupby('track_genre').size()
# Imprimir la cantidad de canciones por género en X_train
print("Cantidad de canciones por género en df_model:")
print(songs_per_genre)
# Definir las clases para subsampling y oversampling para los conjuntos de Training
over_classes = ['grunge', 'sertanejo', 'emo', 'anime', 'chill']
sub_class = ['pop']
# Configuración deseada de Random Oversampling y Undersampling
target_samples_over = 2000
target_samples_sub = 2000
ros = RandomOverSampler(sampling_strategy={cls: target_samples_over for cls in over_classes})
rus = RandomUnderSampler(sampling_strategy={cls: target_samples_sub for cls in sub_class})
# Aplicar Random Oversampling y Undersampling
X_resampled, y_resampled = ros.fit_resample(X_train, y_train)
X_resampled, y_resampled = rus.fit_resample(X_resampled, y_resampled)
# Imprimir resultado de X_train como resampled
print('\n Valores finales de X_resampled que será usado para el entrenamiento')
print(y_resampled.value_counts())
Cantidad de canciones por género en df_model: track_genre anime 1000 chill 2000 emo 1000 grunge 1000 pop 4000 sertanejo 1000 dtype: int64 Valores finales de X_resampled que será usado para el entrenamiento track_genre anime 2000 chill 2000 emo 2000 grunge 2000 pop 2000 sertanejo 2000 Name: count, dtype: int64
Support Vector Machine (SVM)¶
### Modelo Base SVM ###
# Instanciar el modelo
nb_clf = SVC() # kernel='rbf'
# Entrenar el modelo
nb_clf.fit(X_resampled, y_resampled)
# Predecir
y_pred = nb_clf.predict(X_test)
# Resultados
print(classification_report(y_test, y_pred))
precision recall f1-score support
anime 0.45 0.53 0.49 200
chill 0.61 0.61 0.61 400
emo 0.25 0.23 0.24 200
grunge 0.47 0.74 0.58 200
pop 0.76 0.57 0.65 800
sertanejo 0.60 0.80 0.69 200
accuracy 0.58 2000
macro avg 0.52 0.58 0.54 2000
weighted avg 0.60 0.58 0.58 2000
### Modelo GridSearch SVM ###
# Parámetros a explorar mediante GridSearchCV
param_grid = {'C': [0.1, 1, 10],
'gamma': [1, 0.1],
'kernel': ['rbf', 'linear']}
# Instanciar el modelo
svm_clf = SVC()
# Instanciar GridSearchCV
grid_search = GridSearchCV(svm_clf, param_grid, cv=5, n_jobs=-1)
# Entrenar GridSearchCV para encontrar los mejores hiperparámetros
grid_search.fit(X_train, y_train)
# Mejores hiperparámetros encontrados
best_params = grid_search.best_params_
print("Mejores hiperparámetros encontrados:", best_params)
# Entrenar el modelo SVM con los mejores hiperparámetros encontrados
best_svm_clf = SVC(**best_params)
best_svm_clf.fit(X_train, y_train)
# Predecir con el modelo mejorado
y_pred_best = best_svm_clf.predict(X_test)
# Resultados del modelo mejorado
print("Resultados del modelo SVM con mejores hiperparámetros:")
print(classification_report(y_test, y_pred_best))
Mejores hiperparámetros encontrados: {'C': 10, 'gamma': 0.1, 'kernel': 'rbf'}
Resultados del modelo SVM con mejores hiperparámetros:
precision recall f1-score support
anime 0.60 0.42 0.49 200
chill 0.66 0.53 0.59 400
emo 0.46 0.11 0.18 200
grunge 0.57 0.69 0.62 200
pop 0.63 0.88 0.74 800
sertanejo 0.80 0.56 0.66 200
accuracy 0.64 2000
macro avg 0.62 0.53 0.55 2000
weighted avg 0.63 0.64 0.61 2000
from sklearn.metrics import confusion_matrix
cm = confusion_matrix(y_test, y_pred_best, normalize='true') # calcula valores de la matriz de confusión
fig, ax = plt.subplots()
labels = np.array(['anime', 'chill', 'emo', 'grunge', 'pop', 'sertanejo'])
ax = sns.heatmap(cm, annot=True, cmap='coolwarm', xticklabels=labels, yticklabels=labels) # transforma la matriz en un heatmap para su visualización
ax.set_title('Confusion Matrix \n')
ax.set_xlabel('Predicted label')
ax.set_ylabel('True label')
plt.xticks()
plt.yticks(rotation=0)
plt.show()
GradientBoost¶
# Intanciamos el modelo
GB_clf = GradientBoostingClassifier()
# Entrenar el modelo
GB_clf.fit(X_resampled, y_resampled)
GradientBoostingClassifier()In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
GradientBoostingClassifier()
# Predecir
y_pred = GB_clf.predict(X_test)
# Resultados
print(classification_report(y_test, y_pred))
precision recall f1-score support
anime 0.50 0.56 0.53 200
chill 0.60 0.61 0.61 400
emo 0.27 0.29 0.28 200
grunge 0.57 0.68 0.62 200
pop 0.76 0.62 0.69 800
sertanejo 0.61 0.79 0.69 200
accuracy 0.60 2000
macro avg 0.55 0.59 0.57 2000
weighted avg 0.62 0.60 0.61 2000
# Definimos semilla
np.random.seed(7)
# Set scoring metric
score = make_scorer(precision_score, average='macro')
# Configure tuned_parameters
# Los parametros de GBC son loss, learning_rate, n_estimators, subsample,
# criterion, min_samples_split, min_samples_leaf, max_depth, max_features
tuned_parameters = {
"learning_rate": [0.1],
"n_estimators": [10, 100],
"max_depth": [3, 8],
}
# Construcción del clf para gridsearch
clf_GB = GridSearchCV(
GradientBoostingClassifier(),
param_grid=tuned_parameters,
cv=3,
scoring='accuracy',
n_jobs=-1
)
# Entrenar clf
clf_GB.fit(X_resampled, y_resampled)
print("Mejor combinación de parámetros:")
print(clf_GB.best_params_)
y_pred_best = clf_GB.predict(X_test)
print(classification_report(y_test, y_pred_best))
Mejor combinación de parámetros:
{'learning_rate': 0.1, 'max_depth': 8, 'n_estimators': 100}
precision recall f1-score support
anime 0.53 0.52 0.53 200
chill 0.58 0.64 0.61 400
emo 0.24 0.22 0.23 200
grunge 0.66 0.67 0.66 200
pop 0.76 0.75 0.75 800
sertanejo 0.73 0.70 0.72 200
accuracy 0.64 2000
macro avg 0.58 0.58 0.58 2000
weighted avg 0.64 0.64 0.64 2000
from sklearn.metrics import confusion_matrix
cm = confusion_matrix(y_test, y_pred_best, normalize='true') # calcula valores de la matriz de confusión
fig, ax = plt.subplots()
labels = np.array(['anime', 'chill', 'emo', 'grunge', 'pop', 'sertanejo'])
ax = sns.heatmap(cm, annot=True, cmap='coolwarm', xticklabels=labels, yticklabels=labels) # transforma la matriz en un heatmap para su visualización
ax.set_title('Confusion Matrix \n')
ax.set_xlabel('Predicted label')
ax.set_ylabel('True label')
plt.xticks()
plt.yticks(rotation=0)
plt.show()
# Intento de hacer cross validation
from sklearn.model_selection import cross_validate
clf_GB = GradientBoostingClassifier(learning_rate=0.3, max_depth=8, n_estimators=100)
scoring = ['precision_macro', 'recall_macro', 'accuracy', 'f1_macro']
# Configurando validación cruzada
cv_results = cross_validate(clf_GB, X, y, cv=5, scoring=scoring)
print('Promedio Precision:', np.mean(cv_results['test_precision_macro']))
print('Promedio Recall: ', np.mean(cv_results['test_recall_macro']))
print('Promedio F1-score: ', np.mean(cv_results['test_f1_macro']))
print('Promedio Accucary: ', np.mean(cv_results['test_accuracy']))
Promedio Precision: 0.5911450125821642 Promedio Recall: 0.5537500000000001 Promedio F1-score: 0.565136144507209 Promedio Accucary: 0.6327
Bayes¶
# Modelo Bayesiano
clf = GaussianNB()
clf.fit(X_resampled, y_resampled)
y_pred = clf.predict(X_test)
print(classification_report(y_test, y_pred))
precision recall f1-score support
anime 0.46 0.24 0.32 200
chill 0.54 0.42 0.48 400
emo 0.25 0.17 0.20 200
grunge 0.50 0.53 0.51 200
pop 0.55 0.16 0.24 800
sertanejo 0.18 0.92 0.31 200
accuracy 0.33 2000
macro avg 0.41 0.41 0.34 2000
weighted avg 0.47 0.33 0.33 2000
##Cuadricula de parámetros
param_grid = {
'var_smoothing': np.logspace(0, -9, num=100)
}
GridSearchCV
clf = GridSearchCV(estimator=GaussianNB(),
param_grid=param_grid,
cv=5,
n_jobs=-1,
scoring='accuracy')
clf.fit(X_train, y_train)
print("Mejores parámetros encontrados por GridSearchCV:")
print(clf.best_params_)
y_pred_best = clf.predict(X_test)
print(classification_report(y_test, y_pred_best))
Mejores parámetros encontrados por GridSearchCV:
{'var_smoothing': 0.2848035868435802}
precision recall f1-score support
anime 0.69 0.20 0.31 200
chill 0.58 0.37 0.45 400
emo 0.31 0.09 0.13 200
grunge 0.45 0.69 0.54 200
pop 0.56 0.82 0.67 800
sertanejo 0.70 0.56 0.62 200
accuracy 0.56 2000
macro avg 0.55 0.45 0.46 2000
weighted avg 0.56 0.56 0.52 2000
from sklearn.metrics import confusion_matrix
cm = confusion_matrix(y_test, y_pred_best, normalize='true') # calcula valores de la matriz de confusión
fig, ax = plt.subplots()
labels = np.array(['anime', 'chill', 'emo', 'grunge', 'pop', 'sertanejo'])
ax = sns.heatmap(cm, annot=True, cmap='coolwarm', xticklabels=labels, yticklabels=labels) # transforma la matriz en un heatmap para su visualización
ax.set_title('Confusion Matrix \n')
ax.set_xlabel('Predicted label')
ax.set_ylabel('True label')
plt.xticks()
plt.yticks(rotation=0)
plt.show()
Conclusión¶
Inicialmente se consideró trabajar con las 50 clases más populares, pero debido a la complejidad y mal desempeño del modelo, se redujo al top 10. Manteniendo el merge de clases similares y técnicas de sampling. Una vez realizado esto, se trabajó con las 6 clases restantes, notando una inmediata mejoría en el rendimiento del modelo, concluyendo que el reducir la cantidad de clases, ayuda significativamente el desempeño y complejidad de un modelo.
Dentro del pre-procesamiento se construyeron tres dataframes, uno con los datos originales procesados, otro agregando estandarización y otro agregando normalización. A partir de los resultados se concluyó que la estandarización fue la técnica que más ayudó al desempeño de los modelos, permitiendoles mejorar cerca de 0.20 puntos en accuracy y hasta 0.43 puntos en la precision macro avg.
Luego, una vez entrenados y testeados los modelos, se analizaron los resultados de las métricas obtenidas, en donde se obtuvo que:
SVM Model:
El modelo base tuvo un desempeño de 0.52 en precision (macro avg) y 0.58 en accuracy. Mientras que al utilizar GridSearch se obtuvo 0.62 en precision (macro avg) y 0.64 en accuracy, mejorando en comparación al modelo base con los hiperparámetros C = 10, gamma = 0.1 y kernel = rbf y una Cross Validation=5.
Gradient Boost Model:
El modelo base tuvo un desempeño de 0.52 en precision (macro avg) y 0.58 en accuracy. Mientras que al utilizar GridSearch se obtuvo 0.59 en precision (macro avg) y 0.64 en accuracy, mejorando en comparación al modelo base con los hiperparámetros max_depth=8 y n_estimators=100, utilizando una Cross Validation=3.
Posteriormente se entreno con una Cross Validation de 5 y learning rate de 0.3, obteniendo 0.59 en precision (macro avg) y 0.63 en accuracy, no mejorando así al incrementar la Cross Validation y el learning rate.
Bayes Model:
El modelo base tuvo un desempeño de 0.41 en precision (macro avg) y 0.35 en accuracy. Mientras que al utilizar GridSearch se obtuvo 0.55 en precision (macro avg) y 0.56 en accuracy. Mejorando en comparación al modelo base con los parámetros de var_smoothing=0.284 y Cross Validation = 5.
Como acotación, en las matrices de confusión obtenidas a partir de los resultados de cada modelo, se observa que la etiqueta emo es la que presenta métricas más bajas, lo que nos indica que el género es difícil de predecir en base a sus atributos musicales. Esto significa que en sus datos no existen caracteristicas musicales particulares que esten relacionadas entre si para darle una caracterización unica a esta clase por sobre las demás. En particular para el género emo, la letra de la canción es muy relevante, lo que podría permitir esta distinción en caso de poder trabajar directamente con ella.
Finalmente, en base a los resultados obtenidos, el mejor modelo realizado fue SVM, empatando con Gradient Boost en accuracy pero siendo superior en precision, métrica a la que damos prioridad de acuerdo a lo explicado en la metodología. Por otra parte, el modelo que peor se desempeño fue el de Bayes, levemente por debajo de los otros dos. Además, se concluyó efectivamente que las técnicas de pre-procesamiento, sampling y búsqueda de hiperparámetros mediante GridSearch fueron efectivas y permitieron mejorar los modelos.
Volviendo a la pregunta, ¿Es posible predecir el género al que pertenece una canción de acuerdo a las variables musicales tales como el tempo, la valencia musical, cantidad de beats, entre otros? La respuesta a la que llegamos es que si, se puede predecir el género al que pertenece una canción en base a atributos musicales, sin embargo, para aquellos casos en que los resultados son más bajos de lo esperado, es posible seguir adecuando los parámetros y realizar técnicas adicionales para que esta predicción tenga mayor fiabilidad.
Contribución miembros Hito 2¶
| Tarea | Encargado/a |
|---|---|
| Trabajo en la Presentación | Scarlett Plaza |
| Vicente Thiele | |
| Trabajo en el Informe | |
| Javiera Romero | |
| Patricio Espinoza | |
| Rodrigo Díaz | |
| Mejorar Hito 1 | |
| ㅤㅤㅤㅤReducción de matrices de correlación | |
| Javiera Romero | |
| ㅤㅤㅤㅤMatriz de correlación para géneros y general | |
| Javiera Romero | |
| ㅤㅤㅤㅤMejorar fase exploratoria | |
| Patricio Espinoza | |
| ㅤㅤㅤㅤConclusión Hito 1 | |
| Patricio Espinoza | |
| Propuestas de las metodologías 1, 2 y 3 | |
| Javiera Romero | |
| Patricio Espinoza | |
| Rodrigo Díaz | |
| Scarlett Plaza | |
| Vicente Thiele | |
| Preprocesamiento | |
| Patricio Espinoza | |
| Vicente Thiele | |
| Train/Test split & Resampling: Bayes | |
| Vicente Thiele | |
| Rodrigo Díaz | |
| Train/Test split & Resampling: SVM | |
| Patricio Espinoza | |
| Javiera Romero | |
| Train/Test split & Resampling: GradientBoost | |
| Scarlett Plaza | |
| Implementación modelo y métricas de evaluación: Bayes | |
| Vicente Thiele | |
| Rodrigo Díaz | |
| Implementación modelo y métricas de evaluación: SVM | |
| Patricio Espinoza | |
| Javiera Romero | |
| Implementación modelo y métricas de evaluación: GradientBoost | |
| Scarlett Plaza | |
| Conclusión Hito 2 | |
| Patricio Espinoza |
Informe Hito 3¶
Mejorar Hito 2¶
Agregar significado a las matrices¶
Para hacer una analisis más en profundidad de las matrices de confusion y su significado, tomaremos en cuenta los resultados obtenidos para el clasificador SVM.
Si el valor de la diagonal es cercano a 1, podemos decir que la clasificación de esa clase se esta haciendo bien. Mientras que un valor cercano a 0 da muestra de un mal desempeño, para este caso en particular podemos notar que el valor más bajo de esta diagonal se encuentra para la clasificacion del genero Emo (con un valor de 0.11), la razon de este valor tiene su explicación, y es que en sus datos no existen caracteristicas musicales particulares que esten relacionadas entre si para darle una caracterización unica a esta clase por sobre las demás. También tenemos que tener en cuenta que en nuestro dataset no se incluyen las letras de las canciones, lo que en el caso del Emo puede que sea más relevante para clasificarlo en esa categoria que sus caracateristicas musicales.
Para este analisis también podemos tomar en cuenta la categoria Anime que es la segunda más baja (0.42) y a pesar de no ser tan mala metrica como la obtenida por Emo, podriamos esperar más y quizas podamos utilizar la letra de las canciones y detectar cuando las letras estan en japones que seria algo distintivo de este genero además de sus caracteristicas musicales. En cualquier caso esta feature enriqueceria el estudio de los generos musicales.
Este analisis nos permite ampliar la vision que podemos tener sobre los generos musicales y las caracteristicas o features que lo componen, ya que en ciertos casos los valores no-numericos como la letra de la cancion pueden tener un valor más significativo que estas. Un estudio más profundo de los generos musicales deberia entonces incluir metodologias de NLP para alcanzar un estudio completo.
Importar¶
from sklearn.cluster import KMeans
from sklearn.neighbors import NearestNeighbors
# Evaluacion
from sklearn.metrics import silhouette_score
import pandas as pd
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.cluster import DBSCAN
from sklearn.metrics.pairwise import cosine_similarity
from sklearn.metrics import silhouette_score
import scipy.cluster.hierarchy as sch
import matplotlib.pyplot as plt
from sklearn.cluster import AgglomerativeClustering
Metodologia 2¶
Metodología 2: ¿Existen géneros que, pese a ser distintos, sus canciones tienen atributos similares? ¿Y viceversa?
Preprocesamiento
Para responder esta pregunta se aplicarán técnicas de clustering debido a que se busca encontrar similitudes entre grupos de datos. Para empezar se hará un preprocesamiento similar al del método 1, donde se eliminarán columnas que no aportan información relevante en relación con los atributos musicales, estás corresponden a las columnas “track_id “popularity”, “album_name”, “artists”, “duration_ms” “track_name” y “Unnamed: 0” en este caso no se harán merge de géneros como en el método anterior, ya que de hacerlo podría verse afectado el resultado del método. Dado que los datos cuentan con muchos géneros musicales se elegirán 5 para hacer el proceso más simple, dichos géneros serán elegidos tal que podamos visualizar que sucede tanto en casos donde canciones de distintos géneros no tienen características parecidas como cuando si.
Además dado que los datos sobre las características musicales (valence, tempo, loudness, etc) están evaluados en rangos numéricos distintos, a estos se les aplicará un escalamiento tal que influyan de manera equitativa en el cálculo posterior de clusters. Por último se definirán los atributos relevantes para definir la similitud entre canciones, estos corresponden a “explicit”, “danceability”, “energy”, “key”, “loudness”, “mode”, “speechiness”, “acousticness”, “instrumentalness”, “liveness”, “valence”, “tempo” y “time_signature” dado que son los atributos que más caracterizan a una canción de otra en contexto del género al que pertenecen.
Implementación y evaluación de los modelos
Las técnicas específicas de clustering que se utilizaran son K-means, DBSCAN y clustering jerárquico, K-means nos servirá gracias a su simpleza mientras que DBSCAN nos permitirá manejar ruido de manera más certera, por último queremos ver que sucedera en comparacion con clustering jerárquico. Dado que en nuestro caso sabemos las cantidades de clusters que queremos, que corresponde a la cantidad de géneros que estamos analizando para luego ver el solapamiento entre clusters, se utilizará ese numero para el método, pero para propositos de comparación también se buscará un óptimo con el método del codo para ver que indica sin el conocimiento previo de tener 5 géneros, en el caso de DBSCAN se aplicará la técnica de la rodilla para encontrar la cantidad de eps. Luego de aplicar los métodos estos serán comparados y nos quedaremos con el que dé mejores resultados, para evaluar su rendimiento en el caso de K-means se utilizarán los coeficientes de cohesión y separación de forma independiente para cada cluster ya que se trabajará con una cantidad reducida de estos, y para los métodos k-means y dbscan utilizaremos Matriz de similitud y Coeficiente de Silhoutte esto dado que ya que se utilizará DBSCAN la matriz de similitud puede no darnos las mejores conclusiones por sí sola.
Para la visualización de los datos se creará una columna en el data frame que contenga el atributo labels_ para poder así visualizar en base a esto y los géneros. Se reducirán dimensiones utilizando técnicas de PCA para así poder contar con una mejor visualización de los resultados al
ayudarnos a mantener una buena varianza y pocas dimensiones logrando obtener resultados visuales más fáciles de interpretar. Para poder ver la distribución de géneros en cada cluster utilizaremos gráficos de barra que nos mostrarán la proporción de canciones pertenecientes a cada género en cada cluster.
Preprocesamiento¶
# Se genera df_model_m2
#Se escogen generos a traves de la matriz de correlacion eligiendo generos tanto con similitudes y con diferencia
df_model_m2 = df[(df['track_genre'] == 'latino' ) | (df['track_genre'] == 'piano') | (df['track_genre'] == 'edm') | (df['track_genre'] == 'jazz') | (df['track_genre'] == 'pop')]
#Se filtran los atributos relevantes
features = ["danceability", "energy", "loudness", "speechiness", "acousticness", "instrumentalness", "liveness", "valence", "tempo"]
X = df_model_m2[features]
#X = df_model
#Escalar las características
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
Kmeans¶
K-means con método del codo¶
sse = []
clusters = list(range(1, 16))
for k in clusters:
kmeans = KMeans(n_clusters=k, n_init=10).fit(X)
sse.append(kmeans.inertia_)
plt.plot(clusters, sse, marker="o")
plt.title("Metodo del codo de 1 a 15 clusters")
plt.grid(True)
plt.show()
#Creamos el modelo con k = 3 dado el gráfico anterior
kmeans_m21 = KMeans(n_clusters=3, random_state=42) # Ajusta el número de clusters según el análisis previo
kmeans_m21.fit(X_scaled)
df_model_m2['cluster'] = kmeans_m21.labels_
#Reducción de dimensionalidad para visualización utilizando PCA
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X_scaled)
df_model_m2['pca_one'] = X_pca[:, 0]
df_model_m2['pca_two'] = X_pca[:, 1]
#centers=kmeans_m21.cluster_centers_
#plt.figure(figsize=(12, 8))
#sns.scatterplot(x='pca_one', y='pca_two', hue='cluster', palette='tab10', data=df_model_m2, alpha=0.6)
#plt.scatter(centers[:, 0], centers[:, 1], s=200, edgecolors='r')
#plt.title('Clusters visualizados en el espacio PCA')
#plt.show()
#Mostrar los resultados
plt.figure(figsize=(12, 8))
sns.scatterplot(x='pca_one', y='pca_two', hue='cluster', palette='tab10', data=df_model_m2, alpha=0.6)
plt.title('Clusters visualizados en el espacio PCA')
plt.show()
/usr/local/lib/python3.10/dist-packages/sklearn/cluster/_kmeans.py:870: FutureWarning: The default value of `n_init` will change from 10 to 'auto' in 1.4. Set the value of `n_init` explicitly to suppress the warning warnings.warn( <ipython-input-44-4652d6b61567>:4: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy df_model_m2['cluster'] = kmeans_m21.labels_ <ipython-input-44-4652d6b61567>:10: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy df_model_m2['pca_one'] = X_pca[:, 0] <ipython-input-44-4652d6b61567>:11: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy df_model_m2['pca_two'] = X_pca[:, 1]
#Analizamos la composición de géneros dentro de cada cluster
genre_distribution = df_model_m2.groupby('cluster')['track_genre'].value_counts(normalize=True).unstack()
#Gráfico de barras para la distribución de géneros por cluster
genre_distribution.plot(kind='bar', stacked=True, figsize=(12, 8))
plt.title('Distribución de géneros dentro de cada cluster')
plt.xlabel('Cluster')
plt.ylabel('Proporción de géneros')
plt.legend(title='Género')
plt.show()
df_model_m2.groupby('cluster')['track_genre'].value_counts().unstack().sum(axis=1)
cluster 0 3061.0 1 1330.0 2 609.0 dtype: float64
Modelo k-means con k igual a cantidad de géneros ocupados¶
#Creamos modelo con k=5 por los 5 géneros
kmeans_m2 = KMeans(n_clusters=5, random_state=42)
kmeans_m2.fit(X_scaled)
df_model_m2['cluster'] = kmeans_m2.labels_
#Reducción de dimensionalidad para visualización con PCA
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X_scaled)
df_model_m2['pca_one'] = X_pca[:, 0]
df_model_m2['pca_two'] = X_pca[:, 1]
#Mostrar resultados
plt.figure(figsize=(12, 8))
sns.scatterplot(x='pca_one', y='pca_two', hue='cluster', palette='tab10', data=df_model_m2, alpha=0.6)
plt.title('Clusters visualizados en el espacio PCA')
plt.show()
/usr/local/lib/python3.10/dist-packages/sklearn/cluster/_kmeans.py:870: FutureWarning: The default value of `n_init` will change from 10 to 'auto' in 1.4. Set the value of `n_init` explicitly to suppress the warning warnings.warn( <ipython-input-47-12709ba2d795>:4: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy df_model_m2['cluster'] = kmeans_m2.labels_ <ipython-input-47-12709ba2d795>:9: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy df_model_m2['pca_one'] = X_pca[:, 0] <ipython-input-47-12709ba2d795>:10: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy df_model_m2['pca_two'] = X_pca[:, 1]
#Paso 3: Evaluación de los Clusters
#Analizar la composición de géneros dentro de cada cluster
genre_distribution = df_model_m2.groupby('cluster')['track_genre'].value_counts(normalize=True).unstack()
#Gráfico de barras para la distribución de géneros por cluster
genre_distribution.plot(kind='bar', stacked=True, figsize=(12, 8))
plt.title('Distribución de géneros dentro de cada cluster')
plt.xlabel('Cluster')
plt.ylabel('Proporción de géneros')
plt.legend(title='Género')
plt.show()
df_model_m2.groupby('cluster')['track_genre'].value_counts().unstack().sum(axis=1)
cluster 0 607.0 1 2149.0 2 1218.0 3 402.0 4 624.0 dtype: float64
DBSCAN¶
Método de la rodilla¶
k = 18
nbrs = NearestNeighbors(n_neighbors=k).fit(X_scaled)
distances, indices = nbrs.kneighbors(X_scaled)
# Ordenar las distancias al k-ésimo vecino más cercano
distances = np.sort(distances[:,1], axis=0)
# Graficar la distancia al k-ésimo vecino más cercano
plt.figure(figsize=(10, 6))
plt.plot(distances)
plt.axhline(y=1.3, color='r', linestyle='--') # Ajuste el valor para y
plt.xlabel('Puntos ordenados por distancia')
plt.ylabel('Distancia al k-ésimo vecino más cercano')
plt.title('Método de la Rodilla para determinar eps')
plt.grid(True)
plt.show()
Modelo DBSCAN¶
#Creamos el modelo con eps dado por metodo de la rodilla y min samples doble de atributos ocupados
dbscan_m2 = DBSCAN(eps=1.3, min_samples=18)
clusters = dbscan_m2.fit_predict(X_scaled)
df_model_m2['cluster'] = clusters
# Paso 3: Visualización
# Reducción de dimensionalidad para visualización
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X_scaled)
df_model_m2['pca_one'] = X_pca[:, 0]
df_model_m2['pca_two'] = X_pca[:, 1]
# Gráfico de dispersión de los clusters
plt.figure(figsize=(12, 8))
sns.scatterplot(x='pca_one', y='pca_two', hue='cluster', palette='tab10', data=df_model_m2, alpha=0.6)
plt.title('Clusters visualizados en el espacio PCA con DBSCAN')
plt.show()
<ipython-input-51-cf5d9d19a933>:4: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy df_model_m2['cluster'] = clusters <ipython-input-51-cf5d9d19a933>:10: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy df_model_m2['pca_one'] = X_pca[:, 0] <ipython-input-51-cf5d9d19a933>:11: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy df_model_m2['pca_two'] = X_pca[:, 1]
# Analizar la composición de géneros dentro de cada cluster
genre_distribution = df_model_m2.groupby('cluster')['track_genre'].value_counts(normalize=True).unstack()
# Gráfico de barras para la distribución de géneros por cluster
genre_distribution.plot(kind='bar', stacked=True, figsize=(12, 8))
plt.title('Distribución de géneros dentro de cada cluster (DBSCAN)')
plt.xlabel('Cluster')
plt.ylabel('Proporción de géneros')
plt.legend(title='Género')
plt.show()
df_model_m2.groupby('cluster')['track_genre'].value_counts().unstack().sum(axis=1)
cluster -1 486.0 0 3953.0 1 561.0 dtype: float64
Cluster jerárquico¶
from scipy.cluster.hierarchy import dendrogram, linkage
from sklearn.cluster import AgglomerativeClustering
complete = linkage(X_scaled, method="complete")
average = linkage(X_scaled, method="average")
ward = linkage(X_scaled, method="ward")
dendrogram(complete)
plt.title("Linkage: Complete")
plt.show()
complete_9_height = AgglomerativeClustering(n_clusters=None, linkage="complete", distance_threshold=9).fit(X_scaled)
print(f'Clusters generados al cortar en altura 9: {complete_9_height.n_clusters_}')
Clusters generados al cortar en altura 9: 6
df_model_m2['cluster'] = complete_9_height.labels_
# Analizar la composición de géneros dentro de cada cluster
genre_distribution = df_model_m2.groupby('cluster')['track_genre'].value_counts(normalize=True).unstack()
# Gráfico de barras para la distribución de géneros por cluster
genre_distribution.plot(kind='bar', stacked=True, figsize=(12, 8))
plt.title('Distribución de géneros dentro de cada cluster Linkage: Complete')
plt.xlabel('Cluster')
plt.ylabel('Proporción de géneros')
plt.legend(title='Género')
plt.show()
<ipython-input-86-160ecfc4538c>:1: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy df_model_m2['cluster'] = complete_9_height.labels_
# Analizar la composición de géneros dentro de cada cluster
df_model_m2.groupby('cluster')['track_genre'].value_counts().unstack().sum(axis=1)
cluster 0 3543.0 1 206.0 2 1241.0 3 1.0 4 8.0 5 1.0 dtype: float64
Evaluación¶
Cohesion y separacion¶
K-means con método del codo¶
# coeficientes de cohesion y separacion
# Calcular inercia
inertia = kmeans_m21.inertia_
# Calcular Silhouette Score
silhouette_avg = silhouette_score(X_scaled, kmeans_m21.labels_)
# Mostrar métricas de separación y cohesión
print(f"Cohesion: {inertia}")
print(f"Separacion: {silhouette_avg}") ## !!!!!!!!!!!sacar separacion o agregarla
Cohesion: 24966.196439690815 Separacion: 0.2683733726927764
K-means con 5 clusters¶
# coeficientes de cohesion y separacion
# Calcular inercia
inertia = kmeans_m2.inertia_
# Calcular Silhouette Score
silhouette_avg = silhouette_score(X_scaled, kmeans_m2.labels_)
# Mostrar métricas de separación y cohesión
print(f"Cohesion: {inertia}")
print(f"Separacion: {silhouette_avg}")
Cohesion: 19599.663917417798 Separacion: 0.2534982471635583
Matriz de similitud¶
# Matriz de similitud
def sim_matrix(features, labels):
useful_labels = labels >= 0
# primero ordenamos los datos en base al cluster que pertencen
indices = np.argsort(labels[useful_labels])
sorted_features = features[useful_labels][indices]
# calculamos las similitud entre todos los puntos
d = cosine_similarity(sorted_features, sorted_features)
return d
def plot(data, model):
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15,5))
fig.suptitle(f"{model.__class__.__name__}")
ax1.scatter(data[:,0], data[:,1], c=model.labels_)
sim = sim_matrix(data, model.labels_)
im = ax2.imshow(sim, cmap='cividis', vmin=0.0, vmax=1.0)
fig.colorbar(im, ax=ax2)
# Similitud coseno
# Kmeans
plot(X_scaled, kmeans_m21)
plt.show()
# Kmeans
plot(X_scaled, kmeans_m2)
plt.show()
# Jerarquico
plot(X_scaled, complete_9_height)
plt.show()
# DBSCAN
plot(X_scaled, dbscan_m2)
plt.show()
Silhouette¶
# K-Means
print("Dataset X K-means 3", silhouette_score(X_scaled, kmeans_m21.labels_))
print("Dataset X K-means 5", silhouette_score(X_scaled, kmeans_m2.labels_))
print("Dataset X Complete", silhouette_score(X_scaled, complete_9_height.labels_))
# DBSCAN
# Para DBSCAN tenemos que filtrar las labels negativas, ya que representan ruido, no otro cluster
_filter_label = dbscan_m2.labels_ >= 0
# Verificar si hay al menos dos clusters diferentes
unique_labels = len(set(dbscan_m2.labels_[_filter_label]))
if unique_labels > 1:
silhouette_avg = silhouette_score(X_scaled[_filter_label], dbscan_m2.labels_[_filter_label])
print(f"Dataset X DBSCAN Silhouette Score: {silhouette_avg}")
else:
print("No se puede calcular la puntuación de silueta: hay menos de 2 clusters válidos.")
Dataset X K-means 3 0.2683733726927764 Dataset X K-means 5 0.2534982471635583 Dataset X Complete 0.29979636688074907 Dataset X DBSCAN Silhouette Score: 0.45555961507048126
Metodología 3¶
Metodología 3: ¿Los géneros con más canciones explícitas comparten características entre ellos?
Dado que la pregunta tiene un carácter similar a la anterior, es decir, se buscan similitudes entre grupos de datos, las técnicas a utilizar serán de clustering.
Preprocesamiento
Al igual que en las preguntas anteriores, en el dataset original hay atributos que no son necesarios para responder la pregunta y que solo nos molestarán en el desarrollo de los modelos. En este caso, como se desean comparar características musicales para obtener una respuesta, las columnas que se eliminarán corresponden a aquellos atributos que no son imprescindibles al crear una canción como lo son “Unnamed: 0”, “track_id”, “artists”, “album_name”, “track_name”, “popularity” y “duration_ms”.
En cuanto a los atributos que nos interesan, dado que los valores de “key”, “loudness”, “tempo” y “time_signature” se mueven por rangos diferentes a los demás, estos serán normalizados a rango entre 0 y 1, como la mayoría de atributos, para así evitar posibles errores en el entrenamiento del modelo. Por ejemplo, para el método K-means, al haber columnas con un rango muy alto puede que estas generen una dominancia por sobre las demás debido a que en el cálculo de la distancia el valor que más afecta será el mayor haciendo insignificante al atributo de mucho menor valor, lo cual también generaría un sesgo en nuestra respuesta.
Por último, solo se escogerán solo las instancias del dataset cuyo “track_genre” este en los 10 géneros con más canciones explícitas de todo el dataset, ya que la pregunta hace mención justamente a este tipo de géneros, además, serán 10 los escogidos porque será más fácil trabajar con esa cantidad y generar un modelo más correcto aun teniendo una cantidad de géneros considerables.
Implementación y evaluación de los modelos
! Agregar Jerarquico Complete
Las técnicas específicas de clustering a utilizar serán las mencionadas en la pregunta 2, K-means por su simpleza y DBSCAN por su manejo de ruido, estas además se aplicarán de forma muy parecida a la pregunta 2, pues como se mencionó antes, ambas preguntas son de carácter similar, ya que el único cambio que existe entre cada pregunta es que en una se trabaja con 10 géneros escogidos por lo visto en la matriz de correlación (pregunta 2) y en la otra son los 10 géneros con más canciones explícitas en el dataset (pregunta 3). Entonces luego de realizar todo el proceso mencionado en la pregunta 2 (es importante recordar que la cantidad de cluster que queremos, corresponde a la cantidad de géneros que estamos analizando para luego ver el solapamiento entre clusters).
Finalmente, será gracias al gráfico de barras y la evalución de cada modelo que podremos contestar la pregunta y ver como los cluster son formados por un género o por una mezcla de géneros haciendo ver que los géneros con más canciones explícitas en el dataset no comparten características o si lo hacen, respectivamente.
Importar Librerias¶
from sklearn.preprocessing import MinMaxScaler
Preprocesamiento¶
# Se genera el dataframe de la metodologia 3
# Se eliminan las columnas 'Unnamed: 0', 'track_id', 'artists', 'album_name', 'track_name', 'popularity' y 'duration_ms'
df_drop_m3 = df.drop(columns=['Unnamed: 0', 'track_id', 'artists', 'album_name', 'track_name', 'popularity', 'duration_ms'])
# Elige los 10 géneros con más canciones explicitas
# Filtra el DataFrame para obtener solo las filas donde 'explicit' sea True
df_explicit_m3 = df_drop_m3[df_drop_m3['explicit'] == True]
# Selecciona los 10 géneros con más elementos 'explicit=True'
top_10_genres = df_explicit_m3['track_genre'].value_counts().head(10).index.tolist()
# Filtra el DataFrame para obtener solo las filas cuyo 'track_genre' esté en esos 10 géneros
df_filtered_m3 = df_drop_m3[df_drop_m3['track_genre'].isin(top_10_genres)]
# Estandariza los datos en el rango entre 0 y 1
# Separa las columnas numéricas de las columnas no numéricas
df_numeric_m3 = df_filtered_m3.select_dtypes(include=['number'])
df_non_numeric_m3 = df_filtered_m3['track_genre']
# Inicializa el scaler
min_max_scaler = MinMaxScaler()
# Ajusta y transforma las columnas numéricas
scaled_m3 = min_max_scaler.fit_transform(df_numeric_m3)
# Crea un nuevo DataFrame con los datos normalizados
df_numeric_scaled_m3 = pd.DataFrame(scaled_m3, columns=df_numeric_m3.columns)
# Crea el dataframe con las columnas interesantes y los datos estandarizados
df_model_m3 = pd.concat([df_non_numeric_m3.reset_index(drop=True), df_numeric_scaled_m3.reset_index(drop=True)], axis=1)
# Array para usar en la creación de algunos modelos
scaled_m3
# Dataframe final para responder la pregunta 3
df_model_m3
| track_genre | danceability | energy | key | loudness | mode | speechiness | acousticness | instrumentalness | liveness | valence | tempo | time_signature | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | comedy | 0.830946 | 0.730995 | 0.272727 | 0.786145 | 1.0 | 0.301338 | 0.422111 | 0.000000 | 0.295760 | 0.879568 | 0.297378 | 0.75 |
| 1 | comedy | 0.757726 | 0.598992 | 0.727273 | 0.759159 | 1.0 | 0.213209 | 0.147739 | 0.000000 | 0.093522 | 0.755018 | 0.529286 | 0.75 |
| 2 | comedy | 0.692043 | 0.663993 | 0.181818 | 0.772951 | 1.0 | 0.028456 | 0.642211 | 0.000000 | 0.076994 | 0.673700 | 0.327918 | 0.75 |
| 3 | comedy | 0.808334 | 0.340987 | 0.636364 | 0.635703 | 1.0 | 0.493523 | 0.169849 | 0.000000 | 0.062006 | 0.851776 | 0.707393 | 0.75 |
| 4 | comedy | 0.606977 | 0.810996 | 0.545455 | 0.784636 | 1.0 | 0.128265 | 0.018894 | 0.000000 | 0.182835 | 0.809573 | 0.718143 | 0.75 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 9995 | sad | 0.638204 | 0.636993 | 0.636364 | 0.658092 | 1.0 | 0.070716 | 0.383920 | 0.000000 | 0.126373 | 0.200206 | 0.249595 | 0.75 |
| 9996 | sad | 0.740497 | 0.638993 | 0.818182 | 0.633036 | 1.0 | 0.244001 | 0.119598 | 0.000000 | 0.103788 | 0.474009 | 0.583015 | 0.75 |
| 9997 | sad | 0.727576 | 0.418988 | 0.090909 | 0.643634 | 1.0 | 0.173922 | 0.597990 | 0.000000 | 0.096602 | 0.750901 | 0.665876 | 0.75 |
| 9998 | sad | 0.588672 | 0.539991 | 0.636364 | 0.718557 | 1.0 | 0.013910 | 0.050653 | 0.000147 | 0.198234 | 0.229027 | 0.567234 | 0.75 |
| 9999 | sad | 0.772801 | 0.594992 | 0.727273 | 0.669217 | 1.0 | 0.033765 | 0.550754 | 0.000000 | 0.070321 | 0.676788 | 0.664257 | 0.75 |
10000 rows × 13 columns
Kmeans¶
features = ["danceability", "energy", "loudness", "speechiness", "acousticness", "instrumentalness", "liveness", "valence", "tempo"]
X = df_model_m3[features]
K-means con método del codo¶
sse = []
clusters = list(range(1, 16))
for k in clusters:
kmeans = KMeans(n_clusters=k, n_init=10).fit(X)
sse.append(kmeans.inertia_)
plt.plot(clusters, sse, marker="o")
plt.title("Metodo del codo de 1 a 15 clusters")
plt.grid(True)
plt.show()
# Creamos el modelo con clusters x=4 dado el gráfico anterior
x=4
kmeans_m31 = KMeans(n_clusters = x, random_state = 42)
kmeans_m31.fit(scaled_m3)
df_model_m3['cluster'] = kmeans_m31.labels_
# Reducción de dimensionalidad para visualización utilizando PCA
pca = PCA(n_components = 2)
X_pca = pca.fit_transform(scaled_m3)
df_model_m3['pca_one'] = X_pca[:, 0]
df_model_m3['pca_two'] = X_pca[:, 1]
# Mostrar los resultados
plt.figure(figsize = (12, 8))
sns.scatterplot(x = 'pca_one', y = 'pca_two', hue = 'cluster', palette = 'tab10', data = df_model_m3, alpha = 0.6)
plt.title('Clusters visualizados en el espacio PCA')
plt.show()
/usr/local/lib/python3.10/dist-packages/sklearn/cluster/_kmeans.py:870: FutureWarning: The default value of `n_init` will change from 10 to 'auto' in 1.4. Set the value of `n_init` explicitly to suppress the warning warnings.warn(
# Analizamos la composición de géneros dentro de cada cluster
genre_distribution = df_model_m3.groupby('cluster')['track_genre'].value_counts(normalize = True).unstack()
# Gráfico de barras para la distribución de géneros por cluster
genre_distribution.plot(kind = 'bar', stacked = True, figsize = (12, 8))
plt.title('Distribución de géneros dentro de cada cluster')
plt.xlabel('Cluster')
plt.ylabel('Proporción de géneros')
plt.legend(title = 'Género')
plt.show()
Modelo K-Means con K igual a cantidad de géneros ocupados¶
# Creamos modelo con K = x por los x géneros
kmeans_m32 = KMeans(n_clusters = 10, random_state = 42)
kmeans_m32.fit(scaled_m3)
df_model_m3['cluster'] = kmeans_m32.labels_
# Reducción de dimensionalidad para visualización con PCA
pca = PCA(n_components=2)
X_pca = pca.fit_transform(scaled_m3)
df_model_m3['pca_one'] = X_pca[:, 0]
df_model_m3['pca_two'] = X_pca[:, 1]
# Mostrar resultados
plt.figure(figsize = (12, 8))
sns.scatterplot(x = 'pca_one', y = 'pca_two', hue = 'cluster', palette = 'tab10', data = df_model_m3, alpha = 0.6)
plt.title('Clusters visualizados en el espacio PCA')
plt.show()
/usr/local/lib/python3.10/dist-packages/sklearn/cluster/_kmeans.py:870: FutureWarning: The default value of `n_init` will change from 10 to 'auto' in 1.4. Set the value of `n_init` explicitly to suppress the warning warnings.warn(
# Analizar la composición de géneros dentro de cada cluster
genre_distribution = df_model_m3.groupby('cluster')['track_genre'].value_counts(normalize = True).unstack()
# Gráfico de barras para la distribución de géneros por cluster
genre_distribution.plot(kind = 'bar', stacked = True, figsize = (12, 8))
plt.title('Distribución de géneros dentro de cada cluster')
plt.xlabel('Cluster')
plt.ylabel('Proporción de géneros')
plt.legend(title = 'Género')
plt.show()
DBSCAN¶
Método de la rodilla¶
# Se usa el método de la rodilla para determinar eps
nbrs = NearestNeighbors(n_neighbors=26).fit(scaled_m3)
distances, indices = nbrs.kneighbors(scaled_m3)
distances = np.sort(distances, axis=0)
distances = distances[:,25]
fig, ax = plt.subplots()
# Se marca el eps
ax.axhline(y=0.5, color='r', linestyle='--')
ax.plot(distances)
plt.show()
Modelo DBSCAN¶
#Crea el modelo con eps dado por metodo de la rodilla y min samples doble de atributos ocupados
dbscan_m3 = DBSCAN(eps=0.5, min_samples=26)
clusters_m3 = dbscan_m3.fit_predict(scaled_m3)
df_model_m3['cluster'] = clusters_m3
# Visualización
# Reducción de dimensionalidad para visualización
pca_m3 = PCA(n_components=2)
df_pca_m3 = pca_m3.fit_transform(scaled_m3)
df_model_m3['pca_one'] = df_pca_m3[:, 0]
df_model_m3['pca_two'] = df_pca_m3[:, 1]
# Gráfico de dispersión de los clusters
plt.figure(figsize=(12, 8))
sns.scatterplot(x='pca_one', y='pca_two', hue='cluster', palette='tab10', data=df_model_m3, alpha=0.6)
plt.title('Clusters visualizados en el espacio PCA con DBSCAN')
plt.show()
# Analiza la composición de géneros dentro de cada cluster
genre_distribution = df_model_m3.groupby('cluster')['track_genre'].value_counts(normalize=True).unstack()
# Gráfico de barras para la distribución de géneros por cluster
genre_distribution.plot(kind='bar', stacked=True, figsize=(12, 8))
plt.title('Distribución de géneros dentro de cada cluster (DBSCAN)')
plt.xlabel('Cluster')
plt.ylabel('Proporción de géneros')
plt.legend(title='Género')
plt.show()
Cluster Jerárquico¶
complete = linkage(X, method="complete")
average = linkage(X, method="average")
ward = linkage(X, method="ward")
dendrogram(complete)
plt.title("Linkage: Complete")
plt.show()
complete_2_height = AgglomerativeClustering(n_clusters=None, linkage="complete", distance_threshold=2).fit(scaled_m3)
print(f'Clusters generados al cortar en altura 2: {complete_2_height.n_clusters_}')
Clusters generados al cortar en altura 2: 8
df_model_m3['cluster'] = complete_2_height.labels_
# Analizar la composición de géneros dentro de cada cluster
genre_distribution = df_model_m3.groupby('cluster')['track_genre'].value_counts(normalize=True).unstack()
# Gráfico de barras para la distribución de géneros por cluster
genre_distribution.plot(kind='bar', stacked=True, figsize=(12, 8))
plt.title('Distribución de géneros dentro de cada cluster Linkage: Complete')
plt.xlabel('Cluster')
plt.ylabel('Proporción de géneros')
plt.legend(title='Género')
plt.show()
# Analizar la composición de géneros dentro de cada cluster
df_model_m3.groupby('cluster')['track_genre'].value_counts().unstack().sum(axis=1)
cluster 0 3283.0 1 465.0 2 554.0 3 243.0 4 3498.0 5 249.0 6 303.0 7 1405.0 dtype: float64
Evaluación¶
Silhouette¶
# K-Means
print("Dataset 4 K-means", silhouette_score(scaled_m3, kmeans_m31.labels_))
# K-Means
print("Dataset 10 K-means", silhouette_score(scaled_m3, kmeans_m32.labels_))
# K-Means
print("Dataset Complete", silhouette_score(scaled_m3, complete_2_height.labels_))
# DBSCAN
# Para DBSCAN tenemos que filtrar las labels negativas, ya que representan ruido, no otro cluster
_filter_label = dbscan_m3.labels_ >= 0
# Verificar si hay al menos dos clusters diferentes
unique_labels = len(set(dbscan_m3.labels_[_filter_label]))
if unique_labels > 1:
silhouette_avg = silhouette_score(scaled_m3[_filter_label], dbscan_m3.labels_[_filter_label])
print(f"Dataset X DBSCAN Silhouette Score: {silhouette_avg}")
else:
print("No se puede calcular la puntuación de silueta: hay menos de 2 clusters válidos.")
Dataset 4 K-means 0.25745762332371597 Dataset 10 K-means 0.19707393402878373 Dataset Complete 0.17713273351532452 Dataset X DBSCAN Silhouette Score: 0.34561955106655384
Matriz de similitud¶
def sim_matrix(features, labels):
useful_labels = labels >= 0
# primero ordenamos los datos en base al cluster que pertencen
indices = np.argsort(labels[useful_labels])
sorted_features = features[useful_labels][indices]
# calculamos las similitud entre todos los puntos
d = cosine_similarity(sorted_features, sorted_features)
return d
def plot(data, model):
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15,5))
fig.suptitle(f"{model.__class__.__name__}")
ax1.scatter(data[:,0], data[:,1], c=model.labels_)
sim = sim_matrix(data, model.labels_)
im = ax2.imshow(sim, cmap='cividis', vmin=0.0, vmax=1.0)
fig.colorbar(im, ax=ax2)
# Similitud coseno
plot(scaled_m3, kmeans_m31)
plt.show()
plot(scaled_m3, kmeans_m32)
plt.show()
plot(scaled_m3, complete_2_height)
plt.show()
plot(scaled_m3, dbscan_m3)
plt.show()
Conclusión Hito 3¶
Volviendo a la pregunta de la metodología 2: ¿Existen géneros que, pese a ser distintos, sus canciones tienen atributos similares? ¿Y viceversa?
Como sabemos de antemano que trabajamos con 5 géneros, podemos darnos cuenta que aunque el coeficiente de silhouette para DBSCAN es alto, su matriz y grafico de clusters no se desempeña bien, esto ya que se basa unicamente en la densidad para formar el cluster, y aunque esto signifique una alta cohesión y distancia entre clusters, podemos ver que con k-means k=5 nuestro datos tienen una naturaleza dispersa.
Por otro lado, mediante el anáñisis de los patrones obtenido de los modelos, es posible responder la pregunta, en particular vemos que en todos los modelos, para los clusters asignados ocurre que hay canciones pertenecientes a distintos géneros. Es decir, géneros distintos comparten atributos musicales tal que los modelos los asignan en un mismo cluster al aprender estos patrones.
Por lo tanto, la respuesta es que si, existen géneros que pese a ser distintos comparten comparten atributos musicales. Además, veemos que canciones de un mismo género se encuentran asignadas en distintos clusters, por lo que también se responde que si a esta pregunta.
Respecto a la pregunta de la metodología 3: ¿Los géneros con más canciones explícitas comparten características entre ellos?
Mediante el análisis de los clusters obtenidos, se puede responder que si, que aquellos géneros con más canciones explícitas comparten características entre sí. En particular se puede ver que el género comedy suele ser la única excepción a esta regla, pues el resto de géneros se encuentran combinados entre los clusters, indicando una similitud de sus atributos musicales.
En cuanto al desempeño de los modelos, nuevamente vemos que DBSCAN es aquel que tiene la mejor métrica de silhouette pero que al mismo tiempo falla nuevamente en distinguir claramente los clusters. Para k-means por otra parte vemos que al asignarle la cantidad de clusters es capaz de generar una mejor separación entre los clusters y aquellos que son más cercanos entre si. (Cercanos en atributos musicales, pero que pueden ser de géneros distintos)
Trabajo a futuro¶
En base a lo trabajado durante cada pregunta, fue posible observar que el problema de predecir géneros en base a atributos musicales es complejo, y que los modelos empleados tuvieron que usar pocos géneros para tener un desempeño decente, por lo tanto, para mejorar este desempeño y también para extenderlo a una mayor cantidad de géneros es que se plantean las siguientes opciones:
(1) Implementación de Redes Neuronales: Esto permitirá construir un modelo con múltiples capas capaz de aprender mejor los atributos musicales, asimismo, será posible implementar los pesos y técnicas como backpropagation junto con dropout, las cuales permitirán no solo mejorar el modelo sino que extenderlo a más géneros.
(2) Implementación de Procesamiento de Lenguaje Natural: Como se vió en la pregunta 1, hay géneros que no tienen una característica que los distinga claramente del resto, como era el caso de "emo", por lo tanto el desempeño del modelo era poco eficiente al intentar clasificar canciones de dicho género. Una mejora a implementar es NLP, ya que con esto se podrá hacer uso de la letra de las canciones, lo que, en conjunto con técnicas como las capas de Atención, bidireccionalidad y word embeddings mejorarán el modelo y permitirán añadir la letra de la canción como una variable al momento de clasificar el género. Se hace mención específica a técnicas que implementan el contexto en el lenguaje pues esto permitirá clasificar una canción adecuadamente.
Contribución miembros Hito 3¶
| Tarea | Encargado/a |
|---|---|
| Trabajo en la Presentación | |
| Scarlett Plaza | |
| Patricio Espinoza | |
| Trabajo en el Informe | |
| Javiera Romero | |
| Patricio Espinoza | |
| Rodrigo Díaz | |
| Mejorar Hito 2 | |
| ㅤㅤㅤㅤAgregar significado a las matrices | |
| Vicente Thiele | |
| ㅤㅤㅤㅤConclusión Hito 2 | |
| Patricio Espinoza | |
| Preprocesamiento pregunta 2 | |
| Javiera Romero | |
| Preprocesamiento pregunta 3 | |
| Rodrigo Diaz | |
| Kmeans M2 | |
| Javiera Romero | |
| Kmeans M3 | |
| Vicente Thiele | |
| DBscan M2 | |
| Scarlett Plaza | |
| DBscan M3 | |
| Rodrigo Diaz | |
| Jerarquico M3 | |
| Patricio Espinoza | |
| Scarlett Plaza | |
| Jerarquico M2 | |
| Javiera Romero | |
| Scarlett Plaza | |
| Métrica evaluación M2 | |
| Patricio Espinoza | |
| Métrica evaluación M3 | |
| Scarlett Plaza | |
| Conclusión Hito 3 | |
| Patricio Espinoza | |
| Scarlett Plaza |
Material¶
El material usado corresponde al visto durante los laboratorios y el transcurso del curso en clases. Este puede ser encontrado en: